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) }
问题根源
- Repository监听逻辑混乱:
task_date_Uncompleted()每次调用都会创建新的MediatorLiveData,且用observeForever监听子LiveData但未移除,导致数据库清空后旧观察者仍在运行,无法触发空列表更新。 - ViewModel重复监听:
updateResponseUncompletedTask()每次调用都会新增observeForever观察者,旧监听未清理,数据更新逻辑冲突。 - 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
相关产品推荐
相关产品推荐

