如何可靠地更新特定 RecyclerView Item 上的数据?

How to reliably update data on a particular RecyclerView Item?

我的 RecyclerView Item 中的 date/time 只会在我更改它时每隔一段时间更新一次,而不是每次更改都会更新一次。当前进程为:

示例:

我把时间从15:21改成了21:21。在下面的对话框中点击 OK 后,RecyclerView Item 仍然显示 15:21 的旧时间: Picture of RecyclerView Item whose time is 15:21 while the new time selected is 21:21

来自 submitList 的日志:

2022-05-26 17:57:30.314 6461-6461/com.example.ewmreminders V/ReminderListAdapter:165:submitList: Are the two lists the same? true
2022-05-26 17:57:30.315 6461-6461/com.example.ewmreminders V/ReminderListAdapter:168:submitList: Old list (size: 1): [Reminder(id=51, hour=21, minute=21)]
2022-05-26 17:57:30.315 6461-6461/com.example.ewmreminders V/ReminderListAdapter:169:submitList: New list (size: 1): [Reminder(id=51, hour=21, minute=21)]

如果我再次从我的 RecyclerView 项目中单击时间,TimePickerDialog 会正确地将时间默认为 21:21(因此它知道某个地方的时间已更新...)如果我将时间更改为 18:21,我一点击确定,时间就更新了: Picture of RecyclerView Item whose time was updated correctly to 18:21

来自 submitList 的日志:

2022-05-26 18:03:50.931 6461-6461/com.example.ewmreminders V/ReminderListAdapter:165:submitList: Are the two lists the same? false
2022-05-26 18:03:50.932 6461-6461/com.example.ewmreminders V/ReminderListAdapter:168:submitList: Old list (size: 1): [Reminder(id=51, hour=21, minute=21)]
2022-05-26 18:03:50.932 6461-6461/com.example.ewmreminders V/ReminderListAdapter:169:submitList: New list (size: 1): [Reminder(id=51, hour=18, minute=21)]

代码:
适配器:

class ReminderListAdapter(private val timeClickListener: ReminderClickListener,
                          private val reminderListViewModel: ReminderListViewModel,
                          val activity: MainActivity,
                          val reminderDatabaseDao: ReminderDatabaseDao):
    ListAdapter<Reminder, ReminderListAdapter.ReminderListViewHolder>(DiffCallback),
    ItemTouchHelperAdapter
{
    private var mRemindersList = mutableListOf<Reminder>()

    override fun submitList(list: List<Reminder>?) {
        Timber.v("Are the two lists the same? ${mRemindersList == list?.toMutableList()}")
        Timber.v("Old list (size: ${mRemindersList.size}): $mRemindersList")
        Timber.v("New list (size: ${list?.size}): $list")

        mRemindersList = list.toMutableList() //DiffUtil needs a new list to compare to
        super.submitList(mRemindersList)
    }
}

片段:

class ReminderListFragment : Fragment() {
    private lateinit var reminderListViewModel: ReminderListViewModel
    lateinit var adapter: ReminderListAdapter

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        val binding: FragmentReminderListBinding = DataBindingUtil.inflate(
            inflater, R.layout.fragment_reminder_list, container, false)

        val application = requireNotNull(this.activity).application
        val reminderDatabaseDao = ReminderDatabase.getInstance(application).reminderDatabaseDao
        val viewModelFactory = ReminderListViewModelFactory(reminderDatabaseDao, application)
        reminderListViewModel =
            ViewModelProvider(this, viewModelFactory)[ReminderListViewModel::class.java] //.get() and [] are the same thing
        binding.reminderListViewModel = reminderListViewModel
        binding.lifecycleOwner = this
        val manager = LinearLayoutManager(this.context)
        binding.remindersList.layoutManager = manager
        
        adapter = ReminderListAdapter(
            ReminderClickListener(application) { reminder, position ->
                val defaultReminderTime: LocalTime = LocalTime.of(reminder.hour!!, reminder.minute!!)
                val timeClickListener =
                    TimePickerDialog.OnTimeSetListener { view, hourOfDay, minute ->
                        val selectedTime = LocalTime.of(hourOfDay, minute)
                        reminderListViewModel.setTime(reminder, selectedTime) //updates the database
//                        adapter.notifyItemChanged(position)
                    }
                val tP = TimePickerDialogPlus(context, timeClickListener, defaultReminderTime.hour, defaultReminderTime.minute, true) {
                    Timber.v("User clicked the Clear button")
                    reminderListViewModel.setTime(reminder, null)
                }
                tP.show()
            },
            reminderListViewModel,
            activity as MainActivity,
            reminderDatabaseDao)
        binding.remindersList.adapter = adapter
        
        reminderListViewModel.reminders.observe(viewLifecycleOwner, Observer {
            it?.let {
                adapter.submitList(it.toMutableList())
            }
        })
        
        return binding.root
        }
}

查看模型:

class ReminderListViewModel(val reminderDatabaseDao: ReminderDatabaseDao, application: Application) : AndroidViewModel(application) {
    fun setTime(reminder: Reminder, selectedTime: LocalTime?) {
        reminder.hour = selectedTime?.hour
        reminder.minute = selectedTime?.minute
        reminder.save(getApplication()) //eventually calls reminderDatabaseDao.update
    }
    var reminders = reminderDatabaseDao.getAllReminders()
}

道:

interface ReminderDatabaseDao {
    @Update
    fun update(reminder: Reminder)

    @Query("select * ...etc...")
    fun getAllReminders() : LiveData<List<Reminder>>
}

是的,在 VM 的 setTime 函数中,您传递了一个 Reminder 实例并更改了它的数据,改变了对象本身:

fun setTime(reminder: Reminder, selectedTime: LocalTime?) {
    reminder.hour = selectedTime?.hour
    reminder.minute = selectedTime?.minute

并且您传入的 Reminder 适配器内部列表中的对象之一 :

adapter = ReminderListAdapter(
    ReminderClickListener(application) { reminder, position ->
        ...
        reminderListViewModel.setTime(reminder, selectedTime)

所以你明确地将适配器中的 Reminder 更新为 21:21 (这不会在视觉上更新,因为适配器不知道任何关于还没有改变)。


setTime 更改 Reminder 对象后,它使用它来更新数据库:

reminder.save(getApplication())

更新记录,将新列表推送到您用 getAllReminders() 抓取的 LiveData,并且应该包含唯一的 Reminder 个实例。但请记住,您已经通过更新Reminder 更改了适配器的内部列表。您从数据库中获得的列表将与旧列表相同,只是 Reminder 之一的时间发生了变化,对吧?

由于您以相同的方式更改了适配器的内部列表,因此数据将匹配 - 列表将看起来相同(我假设您的 diff 将两个具有相同数据的 Reminder 视为“相同”,如果它们是数据 classes 并检查是否相等,那么是的)。所以适配器不会更新显示 - 就它而言,没有任何改变。


您的对话框显示了正确的时间,因为时间 已设置 Reminder - 适配器尚未显示。我不知道为什么它每 2 次都会以完全相同的步骤工作 - 你可能需要在有用的地方设置一些断点(例如在 setTime,你的观察者在 reminders,里面你的对话框)并调试应用程序,看看那里实际发生了什么,因为它是 运行.


抱歉回答太长,但我想解释一下为什么您会看到这个问题 - 您有效地更新了适配器的内部列表 DB/View 同时建模,所以它们总是匹配的。您可能可以通过 not 改变您传入的 Reminder 来修复它 - 只需复制一份,更改时间,然后保存 that 到数据库。保持旧的不变,因此它反映了旧的状态。如果你使用的是数据class,你可以做

reminder.copy(
    hour = selectedTime?.hour,
    minute = selectedTime?.minute
).save(application) // if you make application a val in your VM constructor you can access it here

这可能会解决所有问题!