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

MVVM架构下LiveData+Room+DiffUtil无法更新RecyclerView问题

清空Room数据库后RecyclerView无法更新的问题

点击弹出菜单的清除按钮清空数据库后,RecyclerView未同步更新。以下是相关代码及问题排查修复方案:


相关代码

仓库代码

package com.example.todoapp.database

import android.app.Application
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
import com.example.todoapp.data.TaskDate

class Repository(private val taskDatabase: TaskDatabase) {

    suspend fun addTask(taskEntity: TaskEntity)=taskDatabase.dao().addTask(taskEntity)
    suspend fun deleteTask(taskEntity: TaskEntity)=taskDatabase.dao().deleteTask(taskEntity)
    suspend fun updateTask(taskEntity: TaskEntity)=taskDatabase.dao().updatetask(taskEntity)
    suspend fun clearTable()=taskDatabase.dao().clearTable()

    fun getAllDatabyDate(date:String)=taskDatabase.dao().getAllDatabyDate(date)
    private fun getAllDateTaskUnCompleted()=taskDatabase.dao().getAllDateTaskUnCompleted()
    private fun getAllDateTaskCompleted()=taskDatabase.dao().getAllDateTaskCompleted()

    fun task_date_Uncompleted():LiveData<List<TaskDate>> {
        val taskDatesLiveData = MediatorLiveData<List<TaskDate>>()
        taskDatesLiveData.addSource(getAllDateTaskUnCompleted()) { uniqueDates ->
            val taskDates = mutableListOf<TaskDate>()
            uniqueDates?.forEach { date ->
                val tasksForDateLive = getAllDatabyDate(date)
                tasksForDateLive.observeForever { tasksForDate ->
                    taskDates.add(TaskDate(date, tasksForDate))
                    taskDatesLiveData.value = taskDates
                }
            }
        }
        return taskDatesLiveData
    }

    fun task_date_Completed():LiveData<List<TaskDate>> {
        val taskDatesLiveData = MediatorLiveData<List<TaskDate>>()

        taskDatesLiveData.addSource(getAllDateTaskCompleted()) { uniqueDates ->
            val taskDates = mutableListOf<TaskDate>()
            uniqueDates?.forEach { date ->
                val tasksForDateLive = getAllDatabyDate(date)
                tasksForDateLive.observeForever { tasksForDate ->
                    taskDates.add(TaskDate(date, tasksForDate))
                    taskDatesLiveData.value = taskDates
                }
            }
        }
        return taskDatesLiveData
    }
}

ViewModel代码

package com.example.todoapp.database

import android.app.Application
import android.util.Log
import android.widget.Toast
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.example.todoapp.data.TaskDate
import kotlinx.coroutines.launch

class TaskViewModel(application: Application,private val repository: Repository):AndroidViewModel(application) {
    fun addTask(taskEntity: TaskEntity)=viewModelScope.launch {
        repository.addTask(taskEntity)
    }

    fun deleteTask(taskEntity: TaskEntity)=viewModelScope.launch {
        repository.deleteTask(taskEntity)
    }
    fun updateTask(taskEntity: TaskEntity)=viewModelScope.launch {
        repository.updateTask(taskEntity)
    }

    fun clearTable()=viewModelScope.launch {
        repository.clearTable()
    }

    private val _response=MutableLiveData<List<TaskDate>>()
    val getTaskDatesUncompleted:LiveData<List<TaskDate>>
        get()=_response

    init{
        updateResponseUncompletedTask()
    }

     fun updateResponseUncompletedTask() =viewModelScope.launch {
         repository.task_date_Uncompleted().observeForever{listUncompletedTask->
             _response.postValue(listUncompletedTask)
     }
    }
}

ShowFragment代码

package com.example.todoapp.Fragment

import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.PopupMenu
import android.widget.Toast
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.todoapp.Adapter.TaskDateAdapter
import com.example.todoapp.MainActivity
import com.example.todoapp.R
import com.example.todoapp.database.TaskViewModel
import com.example.todoapp.databinding.FragmentShowTaskBinding

class ShowTaskFragment : Fragment(R.layout.fragment_show_task){

    private lateinit var binding: FragmentShowTaskBinding
    private lateinit var dateAdapter: TaskDateAdapter
    private lateinit var viewModel: TaskViewModel

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        binding=DataBindingUtil.inflate(inflater,R.layout.fragment_show_task,container,false)
        viewModel=(activity as MainActivity).viewModel
        setUpRecyclerView()
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        loadAddFrag()
        setUpMenu()
    }

    private fun loadAddFrag(){
        binding.btnadd.setOnClickListener{
            findNavController().navigate(R.id.action_showTaskFragment_to_addTaskFragment)
        }
    }

    private fun setUpRecyclerView(){
        dateAdapter= TaskDateAdapter(viewModel)
        binding.taskDateRv.apply {
            setHasFixedSize(true)
            layoutManager=LinearLayoutManager(context,LinearLayoutManager.VERTICAL,false)
            adapter=dateAdapter
        }
        activity?.let {
            viewModel.getTaskDatesUncompleted.observe(viewLifecycleOwner){task_dateList ->
                dateAdapter.differDate.submitList(task_dateList)
            }
        }
    }
    private fun setUpMenu(){
        val popupMenu=PopupMenu(requireContext(),binding.imageMenu)
        popupMenu.inflate(R.menu.main_menu)
        popupMenu.setOnMenuItemClickListener {
            when(it.itemId){
                R.id.clearAll_menu -> {
                    viewModel.clearTable()
                    viewModel.updateResponseUncompletedTask()
                    dateAdapter.notifyDataSetChanged()
                    Toast.makeText(context,"Clear",Toast.LENGTH_SHORT).show()
                    true}
               else ->{
                    findNavController().navigate(R.id.action_showTaskFragment_to_completedTaskFragment)
                   true
               }
            }
        }
        binding.imageMenu.setOnClickListener{
                popupMenu.show()
        }
    }
}

Task适配器代码

package com.example.todoapp.Adapter

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.AsyncListDiffer
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import com.example.todoapp.Helper.SwipeViewHelper
import com.example.todoapp.data.TaskDate
import com.example.todoapp.database.TaskViewModel
import com.example.todoapp.databinding.TaskDateModelBinding

class TaskDateAdapter(val viewModel: TaskViewModel,var type:Int=0):RecyclerView.Adapter<TaskDateAdapter.ViewHolder>() {
    class ViewHolder(var taskDateModelBinding: TaskDateModelBinding):RecyclerView.ViewHolder(taskDateModelBinding.root)

    private lateinit var taskAdapter: TaskAdapter
    private lateinit var taskCompletedAdapter: TaskCompletedAdapter

    private val differCallBack= object :DiffUtil.ItemCallback<TaskDate>(){
        override fun areItemsTheSame(oldItem: TaskDate, newItem: TaskDate): Boolean {
            return oldItem.date==newItem.date &&
                    oldItem.taskDateList==newItem.taskDateList
        }

        override fun areContentsTheSame(oldItem: TaskDate, newItem: TaskDate): Boolean {
           return oldItem==newItem
        }
    }
    val differDate=AsyncListDiffer(this,differCallBack)

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        return ViewHolder(TaskDateModelBinding.inflate(LayoutInflater.from(parent.context),parent,false))
    }

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

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val curr= differDate.currentList[position]
        holder.taskDateModelBinding.taskDateTdm.text=curr.date

        taskCompletedAdapter= TaskCompletedAdapter(viewModel)
        taskAdapter= TaskAdapter(viewModel)
        if(type==1){
            holder.taskDateModelBinding.taskRv.adapter=taskCompletedAdapter
            taskCompletedAdapter.differ.submitList(curr.taskDateList)
        }
        else{
            holder.taskDateModelBinding.taskRv.adapter=taskAdapter
            taskAdapter.differ.submitList(curr.taskDateList)
        }
        setUpSwipe(holder.taskDateModelBinding.taskRv)
    }
    private fun setUpSwipe(recyclerView: RecyclerView){
        var swipeHelper=object: SwipeViewHelper(){
            override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
                when(direction){
                    ItemTouchHelper.LEFT ->{
                        if(type==1){
                            taskCompletedAdapter.deleteTask(viewHolder.adapterPosition)
                        }else {
                            taskAdapter.deleteTask(viewHolder.adapterPosition)
                        }
                    }
                }
                super.onSwiped(viewHolder, direction)
            }
        }
        val touchHelper=ItemTouchHelper(swipeHelper)
        touchHelper.attachToRecyclerView(recyclerView)
    }
}

Room Dao代码

package com.example.todoapp.database

import androidx.lifecycle.LiveData
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Update
import com.example.todoapp.data.TaskDate

@Dao
interface TaskDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun addTask(taskEntity: TaskEntity)

    @Query("Select * from TaskEntity where Date==:date order by id")
    fun getAllDatabyDate(date:String):LiveData<List<TaskEntity>>

    @Query("Select Distinct Date from TaskEntity where TaskStatus=0 order by Date,id ")
    fun getAllDateTaskUnCompleted():LiveData<List<String>>

    @Query("Select Distinct Date from TaskEntity where TaskStatus=1 order by Date,id ")
    fun getAllDateTaskCompleted():LiveData<List<String>>

    @Query("Delete From TaskEntity")
    suspend fun clearTable()

    @Update
    suspend fun updatetask(taskEntity: TaskEntity)

    @Delete
    suspend fun deleteTask(taskEntity: TaskEntity)
}

问题根源

  1. Repository监听逻辑混乱:task_date_Uncompleted()每次调用都会创建新的MediatorLiveData,且用observeForever监听子LiveData但未移除,导致数据库清空后旧观察者仍在运行,无法触发空列表更新。
  2. ViewModel重复监听:updateResponseUncompletedTask()每次调用都会新增observeForever观察者,旧监听未清理,数据更新逻辑冲突。
  3. Fragment手动更新无效:notifyDataSetChanged()无法触发AsyncListDiffer的DiffUtil对比,且LiveData尚未收到数据库清空后的新数据。

修复方案

1. 修复Repository的LiveData逻辑

确保监听可被正确清理,数据库清空时发送空列表:

fun task_date_Uncompleted(): LiveData<List<TaskDate>> {
    val taskDatesLiveData = MediatorLiveData<List<TaskDate>>()
    val dateTasksMap = mutableMapOf<String, LiveData<List<TaskEntity>>>()

    val dateSource = getAllDateTaskUnCompleted()
    taskDatesLiveData.addSource(dateSource) { uniqueDates ->
        // 移除过期日期的监听
        val removedDates = dateTasksMap.keys - (uniqueDates ?: emptyList())
        removedDates.forEach { date ->
            dateTasksMap[date]?.let { taskDatesLiveData.removeSource(it) }
            dateTasksMap.remove(date)
        }

        // 添加新日期的监听
        uniqueDates?.forEach { date ->
            if (!dateTasksMap.containsKey(date)) {
                val tasksLive = getAllDatabyDate(date)
                dateTasksMap[date] = tasksLive
                taskDatesLiveData.addSource(tasksLive) {
                    // 构建最新任务列表
                    val taskDates = dateTasksMap.mapNotNull { (date, _) ->
                        val taskList = taskDatesLiveData.sources.find { it.source === dateTasksMap[date] }?.value as? List<TaskEntity>
                        taskList?.let { TaskDate(date, it) }
                    }
                    taskDatesLiveData.value = taskDates
                }
            }
        }

        // 无数据时直接发送空列表
        if (uniqueDates.isNullOrEmpty()) {
            taskDatesLiveData.value = emptyList()
        }
    }
    return taskDatesLiveData
}

2. 简化ViewModel监听逻辑

移除重复监听,仅初始化时监听一次:

class TaskViewModel(application: Application,private val repository: Repository):AndroidViewModel(application) {
    // 保留原有增删改方法...

    private val _response = MutableLiveData<List<TaskDate>>()
    val getTaskDatesUncompleted: LiveData<List<TaskDate>>
        get() = _response

    init{
        repository.task_date_Uncompleted().observeForever { list ->
            _response.postValue(list)
        }
    }

    // 删除updateResponseUncompletedTask方法
}

3. 修改Fragment清空逻辑

依赖LiveData自动更新,移除手动触发代码:

private fun setUpMenu(){
    val popupMenu=PopupMenu(requireContext(),binding.imageMenu)
    popupMenu.inflate(R.menu.main_menu)
    popupMenu.setOnMenuItemClickListener {
        when(it.itemId){
            R.id.clearAll_menu -> {
                viewModel.clearTable()
                Toast.makeText(context,"已清空所有任务",Toast.LENGTH_SHORT).show()
                true
            }
            else ->{
                findNavController().navigate(R.id.action_showTaskFragment_to_completedTaskFragment)
                true
            }
        }
    }
    binding.imageMenu.setOnClickListener{
        popupMenu.show()
    }
}

4. 优化适配器DiffUtil判断

修正任务列表的对比逻辑:

private val differCallBack= object :DiffUtil.ItemCallback<TaskDate>(){
    override fun areItemsTheSame(oldItem: TaskDate, newItem: TaskDate): Boolean {
        return oldItem.date == newItem.date
    }

    override fun areContentsTheSame(oldItem: TaskDate, newItem: TaskDate): Boolean {
        return oldItem.taskDateList.size == newItem.taskDateList.size && 
               oldItem.taskDateList.containsAll(newItem.taskDateList)
    }
}

5. 复用子适配器避免重复创建

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
    val curr= differDate.currentList[position]
    holder.taskDateModelBinding.taskDateTdm.text=curr.date

    // 复用子适配器
    val existingAdapter = holder.taskDateModelBinding.taskRv.adapter
    if(type==1){
        val adapter = if (existingAdapter is TaskCompletedAdapter) existingAdapter else TaskCompletedAdapter(viewModel)
        holder.taskDateModelBinding.taskRv.adapter = adapter
        adapter.differ.submitList(curr.taskDateList)
    } else {
        val adapter = if (existingAdapter is TaskAdapter) existingAdapter else TaskAdapter(viewModel)
        holder.taskDateModelBinding.taskRv.adapter = adapter
        adapter.differ.submitList(curr.taskDateList)
    }

    setUpSwipe(holder.taskDateModelBinding.taskRv)
}

内容的提问来源于stack exchange,提问作者Arnav Gupta

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 18:20:56