You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android Todo应用:数据库对象更新与跨屏数据传递实现咨询

Todo应用任务更新功能实现方案

我需要为自研Todo应用实现任务更新流程:从任务列表Fragment跳转至任务详情Fragment,点击编辑按钮进入任务编辑Fragment,修改任务标题、描述和日期后,同步更新数据库与RecyclerView,目前不清楚正确的实现方式。


现有代码实现

TaskListFragment

class TaskListFragment : Fragment(R.layout.fragment_task_list) {
    private lateinit var todoAdapter: TodoAdapter
    private var _binding: FragmentTaskListBinding? = null
    private lateinit var todoDao: TodoDao
    private lateinit var viewModel: TodoViewModel
    private val binding: FragmentTaskListBinding
        get() = checkNotNull(_binding)
    private var todos: MutableList<Todo> = mutableListOf()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val appDatabase = AppDatabase.getInstance(this.requireActivity())
        todoDao = appDatabase.todoDao()
        var repository = TaskRepository(todoDao)
        viewModel = ViewModelProvider(this, TodoViewModelFactory(repository)).get(TodoViewModel::class.java)
        repository.allTasks.observe(this, Observer { todoList ->
            todos.clear()
            todos.addAll(todoList)
            todoAdapter.notifyDataSetChanged()

        })

    }
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        _binding = FragmentTaskListBinding.inflate(inflater, container, false)
        return binding.root
    }
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        todoAdapter = TodoAdapter(todos,viewModel)
        binding.rvTodoItems.adapter = todoAdapter
        binding.rvTodoItems.layoutManager = LinearLayoutManager(this.activity)
        binding.btnAddTodo.setOnClickListener {
            NewTaskSheet().show(requireActivity().supportFragmentManager, "NewTaskTag")
        }
        requireActivity().supportFragmentManager.setFragmentResultListener(
            "requestKey",
            this
        ) { requestKey, bundle ->
            val newTodo = bundle.getSerializable("bundleKey") as Todo
            GlobalScope.launch {
                viewModel.insert(newTodo)
            }
            todoAdapter.notifyDataSetChanged()
        }
        binding.rvTodoItems.addItemDecoration(
            DividerItemDecoration(
                binding.rvTodoItems.getContext(),
                DividerItemDecoration.VERTICAL
            )
        )
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}

TodoAdapter

class TodoAdapter(
    private var todos: MutableList<Todo>,
    private val viewModel: TodoViewModel
):RecyclerView.Adapter<TodoAdapter.TodoViewHolder>() {
    class TodoViewHolder( ItemTodoBinding: ItemTodoBinding): RecyclerView.ViewHolder(ItemTodoBinding.root)

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TodoViewHolder {

        val layoutInflater = LayoutInflater.from(parent.context)
        val ItemTodoBinding = ItemTodoBinding.inflate(layoutInflater, parent, false)

        return TodoViewHolder(
            ItemTodoBinding
        )
    }
    private fun toggleStrikeThrough(tvTodoTitle: 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 onBindViewHolder(holder: TodoViewHolder, position: Int) {
          val curTodo = todos[position]
          holder.itemView.apply{
              val tvTodoTitle = findViewById(R.id.tvTodoTitle) as TextView
              tvTodoTitle.text = curTodo.title
              val cbDone = findViewById(R.id.cbDone) as CheckBox
              val deleteBtn = findViewById(R.id.deleteBtn) as Button
              cbDone.isChecked = curTodo.isChecked
              toggleStrikeThrough(tvTodoTitle, curTodo.isChecked)
              cbDone.setOnCheckedChangeListener { _, isChecked ->
                  toggleStrikeThrough(tvTodoTitle, isChecked)
                  curTodo.isChecked = !curTodo.isChecked
              }
              cbDone.setOnClickListener(View.OnClickListener{
                  GlobalScope.launch {
                      viewModel.update(curTodo)
                      viewModel.delete(curTodo)
                  }
                      todos.removeAll { todo ->
                          todo.isChecked
                      }
                      notifyDataSetChanged()
              })

              deleteBtn.setOnClickListener{
                  val alertDialogBuilder = AlertDialog.Builder(this.context)
                  alertDialogBuilder.setMessage("Do you want to delete this task?")
                  alertDialogBuilder.setPositiveButton("Yes"){
                          dialogInterface, which ->
                      Toast.makeText(context,"Delete",Toast.LENGTH_LONG).show()
                      GlobalScope.launch {
                          viewModel.delete(curTodo)
                      }
                  }
                  alertDialogBuilder.setNegativeButton("No"){
                          dialogInterface, which ->
                      Toast.makeText(context,"Cancelled",Toast.LENGTH_LONG).show()
                  }
                  val alertDialog: AlertDialog = alertDialogBuilder.create()
                  alertDialog.setCancelable(false)
                  alertDialog.show()
                  notifyDataSetChanged()
              }

              tvTodoTitle.setOnClickListener {
                  val request = NavDeepLinkRequest.Builder
                      .fromUri("${TASK_DETAIL_URI}?${TASK_BUNDLE_KEY}=${curTodo.title}&${TASK_DESCR_BUNDLE_KEY}=${curTodo.description}&${TASK_DATE_BUNDLE_KEY}=${curTodo.date}".toUri())
                      .build()
                  Log.d("MyApp", curTodo.description)
                  findNavController().navigate(request)


              }
        }
    }

    override fun getItemCount(): Int {
          return todos.size
    }


    companion object{
        const val TASK_DETAIL_URI = "app://taskmaster.taskDetailFragment"
        const val TASK_LIST_URI ="app://taskmaster.taskListFragment"
        const val TASK_EDIT_URI="app://taskmaster.taskEditFragment"
    }

}

TaskDetailFragment

class TaskDetailFragment : Fragment() {
    private var _binding: FragmentTaskDetailBinding? = null
    private val binding: FragmentTaskDetailBinding
        get() = checkNotNull(_binding)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        _binding = FragmentTaskDetailBinding.inflate(inflater, container, false)
        return binding.root

    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val title = arguments?.getString(TASK_BUNDLE_KEY)
        val date = arguments?.getString(TASK_DATE_BUNDLE_KEY)
        val descr = arguments?.getString(TASK_DESCR_BUNDLE_KEY)
        Log.d("MyApp", descr.toString())
        binding.taskTitle.text = title
        binding.selectedDate.text = date
        binding.selectedDescription.text = descr

        binding.btnBack.setOnClickListener {
            val request = NavDeepLinkRequest.Builder
                .fromUri(TASK_LIST_URI.toUri())
                .build()
            findNavController().navigate(request)
        }
        binding.btnEdit.setOnClickListener {
            val request = NavDeepLinkRequest.Builder
                .fromUri("${TodoAdapter.TASK_EDIT_URI}?${TASK_BUNDLE_KEY}=${title}&${TASK_DESCR_BUNDLE_KEY}=${descr}&${TASK_DATE_BUNDLE_KEY}=${date}".toUri())
                .build()
            findNavController().navigate(request)
        }

    }
}

TaskEditFragment

class TaskEditFragment : Fragment() {
    private var _binding: FragmentTaskEditBinding? = null
    private val binding: FragmentTaskEditBinding
        get() = checkNotNull(_binding)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        _binding = FragmentTaskEditBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val title = arguments?.getString(Constants.TASK_BUNDLE_KEY)
        val date = arguments?.getString(Constants.TASK_DATE_BUNDLE_KEY)
        val descr = arguments?.getString(Constants.TASK_DESCR_BUNDLE_KEY)
        binding.title.setText(title)
        binding.date.setText(date)
        binding.description.setText(descr)
        binding.btnSave.setOnClickListener {
            val request = NavDeepLinkRequest.Builder
                .fromUri("${TodoAdapter.TASK_DETAIL_URI}?${Constants.TASK_EDIT_BUNDLE_KEY}=${binding.title.text}&${Constants.TASK_EDIT_DESCR_BUNDLE_KEY}=${binding.description.text}&${Constants.TASK_EDIT_DATE_BUNDLE_KEY}=${binding.date.text}".toUri())
                .build()
            findNavController().navigate(request)
        }
    }

}

TodoViewModel

class TodoViewModel(private val repository: TaskRepository): ViewModel() {
    fun insert(todo: Todo) = viewModelScope.launch {
        repository.insert(todo)
    }

    fun update(todo: Todo) = viewModelScope.launch{
        repository.update(todo)
    }

    fun delete(todo: Todo) = viewModelScope.launch{
        repository.delete(todo)
    }

}
class TodoViewModelFactory(private val repository: TaskRepository) : ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(TodoViewModel::class.java)) {
            @Suppress("UNCHECKED_CAST")
            return TodoViewModel(repository) as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }
}

TaskRepository

class TaskRepository(private val todoDao: TodoDao) {
    val allTasks: LiveData<List<Todo>> = todoDao.getAllTasks()
    @Suppress("RedundantSuspendModifier")
    @WorkerThread
    suspend fun insert(todo: Todo) {
        todoDao.insert(todo)
    }

    suspend fun update(todo: Todo){
        todoDao.update(todo)
    }

    suspend fun delete(todo: Todo){
        todoDao.delete(todo)
    }
}

导航图(task_graph.xml)

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/task_graph"
    app:startDestination="@id/taskListFragment">

    <fragment
        android:id="@+id/taskListFragment"
        android:name="com.bignerdranch.android.taskmaster.TaskListFragment"
        android:label="TaskListFragment">
        <deepLink
            android:id="@+id/deepLink2"
            app:uri="app://taskmaster.taskListFragment" />
    </fragment>
    <fragment
        android:id="@+id/taskDetailFragment"
        android:name="com.bignerdranch.android.taskmaster.TaskDetailFragment"
        android:label="TaskDetailFragment">
        <deepLink
            android:id="@+id/deepLink"
            app:uri="app://taskmaster.taskDetailFragment?taskBundleKey={taskBundleKey}&amp;taskDateBundleKey={taskDateBundleKey}&amp;taskDescrBundleKey={taskDescrBundleKey}"/>
        <argument android:name="taskBundleKey" app:argType="string"/>
        <argument android:name="taskDateBundleKey" app:argType="string"/>
        <argument android:name="taskDescrBundleKey" app:argType="string"/>
    </fragment>
    <fragment
        android:id="@+id/taskEditFragment"
        android:name="com.bignerdranch.android.taskmaster.TaskEditFragment"
        android:label="TaskEditFragment">

        <deepLink
            android:id="@+id/deepLink3"
            app:uri="app://taskmaster.taskEditFragment?taskBundleKey={taskBundleKey}&amp;taskDateBundleKey={taskDateBundleKey}&amp;taskDescrBundleKey={taskDescrBundleKey}" />
        <argument android:name="taskBundleKey" app:argType="string"/>
        <argument android:name="taskDateBundleKey" app:argType="string"/>
        <argument android:name="taskDescrBundleKey" app:argType="string"/>
    </fragment>
</navigation>

任务更新功能修复与实现步骤

1. 传递任务ID而非单独字段

当前代码通过传递任务的标题、描述等字段跳转,无法唯一标识任务(可能存在同名任务),应改为传递任务的ID:

  • 修改Todo实体类,确保有唯一ID字段(通常用@PrimaryKey(autoGenerate = true)注解)
  • 更新导航图,将参数改为taskId(类型为long或int):
<!-- TaskDetailFragment参数 -->
<argument android:name="taskId" app:argType="long"/>
<!-- TaskEditFragment参数 -->
<argument android:name="taskId" app:argType="long"/>
  • 修改TodoAdapter中跳转代码,传递taskId:
tvTodoTitle.setOnClickListener {
    val request = NavDeepLinkRequest.Builder
        .fromUri("${TASK_DETAIL_URI}?taskId=${curTodo.id}".toUri())
        .build()
    findNavController().navigate(request)
}

2. 详情页通过ID加载任务数据

TaskDetailFragment不再直接用参数显示数据,而是通过ViewModel根据ID加载任务:

class TaskDetailFragment : Fragment() {
    private lateinit var viewModel: TodoViewModel
    private var taskId: Long = -1

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        taskId = arguments?.getLong("taskId") ?: -1
        val repository = TaskRepository(AppDatabase.getInstance(requireActivity()).todoDao())
        viewModel = ViewModelProvider(this, TodoViewModelFactory(repository)).get(TodoViewModel::class.java)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewModel.getTaskById(taskId).observe(viewLifecycleOwner) { todo ->
            todo?.let {
                binding.taskTitle.text = it.title
                binding.selectedDate.text = it.date
                binding.selectedDescription.text = it.description
            }
        }

        binding.btnEdit.setOnClickListener {
            val request = NavDeepLinkRequest.Builder
                .fromUri("${TodoAdapter.TASK_EDIT_URI}?taskId=${taskId}".toUri())
                .build()
            findNavController().navigate(request)
        }
    }
}

同时在ViewModel、Repository和Dao中添加根据ID获取任务的方法:

// TodoViewModel
fun getTaskById(id: Long) = repository.getTaskById(id)

// TaskRepository
fun getTaskById(id: Long): LiveData<Todo?> = todoDao.getTaskById(id)

// TodoDao
@Query("SELECT * FROM todo WHERE id = :id")
fun getTaskById(id: Long): LiveData<Todo?>

3. 编辑页更新数据库并返回

TaskEditFragment根据ID加载任务,修改后调用ViewModel更新数据库,完成后返回列表页:

class TaskEditFragment : Fragment() {
    private lateinit var viewModel: TodoViewModel
    private var taskId: Long = -1
    private var originalTodo: Todo? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        taskId = arguments?.getLong("taskId") ?: -1
        val repository = TaskRepository(AppDatabase.getInstance(requireActivity()).todoDao())
        viewModel = ViewModelProvider(this, TodoViewModelFactory(repository)).get(TodoViewModel::class.java)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        viewModel.getTaskById(taskId).observe(viewLifecycleOwner) { todo ->
            todo?.let {
                originalTodo = it
                binding.title.setText(it.title)
                binding.date.setText(it.date)
                binding.description.setText(it.description)
            }
        }

        binding.btnSave.setOnClickListener {
            originalTodo?.let { todo ->
                val updatedTodo = todo.copy(
                    title = binding.title.text.toString(),
                    description = binding.description.text.toString(),
                    date = binding.date.text.toString()
                )
                viewModel.update(updatedTodo)
                findNavController().popBackStack(R.id.taskListFragment, false)
            }
        }
    }
}

4. 利用LiveData自动更新RecyclerView

当前TaskListFragment已经观察了repository.allTasks,只要数据库更新,LiveData会自动通知,此时只需要确保Adapter的数据源和LiveData同步,无需手动调用notifyDataSetChanged():

// TaskListFragment中修改LiveData观察逻辑
repository.allTasks.observe(viewLifecycleOwner) { todoList ->
    todos.clear()
    todos.addAll(todoList)
    todoAdapter.notifyDataSetChanged()
}

注意:使用viewLifecycleOwner而非this,避免内存泄漏。

5. 修复现有代码中的其他问题

  • 删除GlobalScope的使用,改用viewModelScope处理异步操作,避免内存泄漏
  • Todo
相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 23:53:05