如何解决Android中java.lang.NullPointerException: null无法转为TextView问题
问题分析与解决方案
崩溃原因
直接触发崩溃的代码是MainActivity第25行的val tvTodoTitle = findViewById<TextView>(R.id.tvTodoTitle):
tvTodoTitle是RecyclerView列表项布局item_todo.xml中的元素,不存在于MainActivity的布局activity_main.xml中- 调用
findViewById会返回null,而Kotlin的非null类型强制转换(as TextView)直接抛出NullPointerException
此外还有几个逻辑问题:
- 原
TaskViewModel仅保存单个任务的标题/描述,不符合Todo应用管理任务列表的需求 NewTaskSheet的onViewCreated方法错误调用了super.onCreate(savedInstanceState),而非super.onViewCreated(view, savedInstanceState)- Adapter中使用
findViewById绑定视图效率低下,未利用ViewBinding的优势
修复步骤与代码修改
1. 补充缺失的Todo数据类与TaskViewModel
// Todo.kt data class Todo( val title: String, val description: String, var isChecked: Boolean )
// TaskViewModel.kt import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel class TaskViewModel : ViewModel() { // 用LiveData管理任务列表 val taskList = MutableLiveData<MutableList<Todo>>(mutableListOf()) // 添加新任务的方法 fun addTask(title: String, description: String) { val currentList = taskList.value ?: mutableListOf() currentList.add(Todo(title, description, false)) taskList.postValue(currentList) } }
2. 修改MainActivity,移除错误代码并观察任务列表
// MainActivity.kt package com.bignerdranch.android.taskmaster import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import androidx.lifecycle.ViewModelProvider import androidx.recyclerview.widget.LinearLayoutManager import com.bignerdranch.android.taskmaster.databinding.ActivityMainBinding class MainActivity : AppCompatActivity() { private lateinit var todoAdapter: TodoAdapter private lateinit var binding: ActivityMainBinding private lateinit var taskViewModel: TaskViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) taskViewModel = ViewModelProvider(this).get(TaskViewModel::class.java) todoAdapter = TodoAdapter(mutableListOf()) binding.rvTodoItems.adapter = todoAdapter binding.rvTodoItems.layoutManager = LinearLayoutManager(this) binding.btnAddTodo.setOnClickListener { NewTaskSheet().show(supportFragmentManager, "NewTaskTag") } // 观察任务列表变化,更新RecyclerView taskViewModel.taskList.observe(this) { todos -> todoAdapter.updateTodos(todos) } } }
3. 优化TodoAdapter,使用ViewBinding并添加列表更新方法
// TodoAdapter.kt package com.bignerdranch.android.taskmaster import android.graphics.Paint.STRIKE_THRU_TEXT_FLAG import android.view.LayoutInflater import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import com.bignerdranch.android.taskmaster.databinding.ItemTodoBinding class TodoAdapter( private val todos: MutableList<Todo> ) : RecyclerView.Adapter<TodoAdapter.TodoViewHolder>() { class TodoViewHolder(private val binding: ItemTodoBinding) : RecyclerView.ViewHolder(binding.root) { fun bind(todo: Todo) { binding.tvTodoTitle.text = todo.title binding.cbDone.isChecked = todo.isChecked toggleStrikeThrough(binding.tvTodoTitle, todo.isChecked) binding.cbDone.setOnCheckedChangeListener { _, isChecked -> toggleStrikeThrough(binding.tvTodoTitle, isChecked) todo.isChecked = isChecked } binding.cbDone.setOnClickListener { todos.removeAll { it.isChecked } notifyDataSetChanged() } } private fun toggleStrikeThrough(tvTodoTitle: android.widget.TextView, isChecked: Boolean) { if (isChecked) { tvTodoTitle.paintFlags = tvTodoTitle.paintFlags or STRIKE_THRU_TEXT_FLAG } else { tvTodoTitle.paintFlags = tvTodoTitle.paintFlags and STRIKE_THRU_TEXT_FLAG.inv() } } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TodoViewHolder { val binding = ItemTodoBinding.inflate(LayoutInflater.from(parent.context), parent, false) return TodoViewHolder(binding) } override fun onBindViewHolder(holder: TodoViewHolder, position: Int) { holder.bind(todos[position]) } override fun getItemCount(): Int { return todos.size } // 更新任务列表的方法 fun updateTodos(newTodos: List<Todo>) { todos.clear() todos.addAll(newTodos) notifyDataSetChanged() } }
4. 修复NewTaskSheet的生命周期调用并完善保存逻辑
// NewTaskSheet.kt package com.bignerdranch.android.taskmaster import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.lifecycle.ViewModelProvider import com.bignerdranch.android.taskmaster.databinding.FragmentNewTaskSheetBinding import com.google.android.material.bottomsheet.BottomSheetDialogFragment class NewTaskSheet : BottomSheetDialogFragment() { private lateinit var binding: FragmentNewTaskSheetBinding private lateinit var taskViewModel: TaskViewModel override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { binding = FragmentNewTaskSheetBinding.inflate(inflater, container, false) return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // 修复生命周期调用错误 val activity = requireActivity() taskViewModel = ViewModelProvider(activity).get(TaskViewModel::class.java) binding.btnSave.setOnClickListener { saveAction() } } private fun saveAction() { val title = binding.title.text.toString().trim() val description = binding.description.text.toString().trim() if (title.isNotEmpty()) { // 校验标题不为空 taskViewModel.addTask(title, description) binding.title.setText("") binding.description.setText("") dismiss() } } }
5. 补充NewTaskSheet的布局文件(fragment_new_task_sheet.xml)
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="16dp"> <EditText android:id="@+id/title" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="Task Title" android:textSize="18sp"/> <EditText android:id="@+id/description" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="Task Description" android:layout_marginTop="8dp" android:minLines="3"/> <Button android:id="@+id/btnSave" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Save" android:layout_gravity="end" android:layout_marginTop="16dp"/> </LinearLayout>
内容的提问来源于stack exchange,提问作者Владислава Черкасова
相关产品推荐
相关产品推荐

