如何可靠地更新特定 RecyclerView Item 上的数据?
How to reliably update data on a particular RecyclerView Item?
我的 RecyclerView Item 中的 date/time 只会在我更改它时每隔一段时间更新一次,而不是每次更改都会更新一次。当前进程为:
- 用户通过Date/TimePicker
更新时间
- 更改已保存到 Room 数据库
- 我的片段的“提醒”LiveData 观察器被触发,它调用适配器的重写 submitList 函数
- 对于每~2 次更改,submitList 认为旧列表和新列表是相同的,因为旧列表已经以某种方式更新以反映新更改,即使旧列表 (mRemindersList) 对适配器是私有的,所以我'我不确定它是如何偷偷更新的。
示例:
我把时间从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>>
}
想
- 我尝试调用 adapter.notifyItemChanged(position) 但没有任何改变
是的,在 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
这可能会解决所有问题!
我的 RecyclerView Item 中的 date/time 只会在我更改它时每隔一段时间更新一次,而不是每次更改都会更新一次。当前进程为:
- 用户通过Date/TimePicker 更新时间
- 更改已保存到 Room 数据库
- 我的片段的“提醒”LiveData 观察器被触发,它调用适配器的重写 submitList 函数
- 对于每~2 次更改,submitList 认为旧列表和新列表是相同的,因为旧列表已经以某种方式更新以反映新更改,即使旧列表 (mRemindersList) 对适配器是私有的,所以我'我不确定它是如何偷偷更新的。
示例:
我把时间从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>>
}
想
- 我尝试调用 adapter.notifyItemChanged(position) 但没有任何改变
是的,在 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
这可能会解决所有问题!