使用 LiveData 更新 RecyclerView 中的消息列表而不重新发布整个列表
Updating a list of messages in a RecyclerView using LiveData without reposting the whole list
在连接到蓝牙设备时,我从中收到大量测量结果,我希望在屏幕上显示这些测量结果而不重新发布整个列表。
为此,我使用了一个 ViewModel,如下所示:
class MainViewModel : ViewModel() {
private val logic: MainLogic = MainLogic(this, AppDispatchers)
val messageList = mutableListOf<String>()
val newMessage = SingleLiveEvent<String>()
val clearMessages = SingleLiveEvent<Unit>()
fun addMessage(message: String) {
messageList.add(message)
newMessage.postValue(message)
}
override fun clearMessages() {
messageList.clear()
clearMessages.postValue(null)
}
}
它在视图模型中包含完整列表,应该只将更新发送到主屏幕
我也使用 SingleLiveEvent,如下所示:
我的主要 activity 看起来像这样:
class MainActivity : AppCompatActivity() {
private lateinit var vm: MainViewModel
private lateinit var adapter: MessageAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
vm = ViewModelProviders.of(this).get(MainViewModel::class.java)
adapter = MessageAdapter(vm.messageList)
messages.adapter = adapter
setUpObservers()
}
private fun setUpObservers() {
vm.clearMessages.observe(this, Observer {
adapter.clear()
})
vm.newMessage.observe(this, Observer {
adapter.addMessage(it)
})
}
}
当消息流很慢时,这会正常工作,适配器只会添加新消息,当收到要清除的消息时,它会被清除。
不幸的是,当消息流很快时,很多消息都丢失了,当我旋转屏幕时(因此适配器是从 vm.messageList
重新创建的)所有消息都会显示,所以我没有丢失任何东西
有没有办法在不重新发布整个列表的情况下正确显示列表?
可能的解决方法是将此新消息与以前的消息一起存储在本地数据库(如 Room)中,并按超时更新列表(例如每 2 秒)and/or 按关键消息差异(数据库中的消息数量减去适配器中的消息数量 > 某个阈值)。
要加快从数据库下载消息的速度,您可以使用 Android 架构组件中的页面库。
请参阅:https://developer.android.com/reference/android/arch/paging/PagedList 和
https://developer.android.com/reference/android/support/v7/util/DiffUtil
希望对您有所帮助
这里的问题是您使用的 SingleLiveEvent
一次举办一个活动。因此,如果您连续多次调用 newMessage.postValue(message)
,新消息会覆盖尚未发送的前一条消息。
绝对可以在不重新加载整个数据集的情况下更新列表。假设您正在使用 RecylerView
并调用 notifyItemInserted
properly, you just need to pass all the new messages to your activity. There are quite a few ways to do so, e.g. use rxJava or kotlin coroutines channels。它们都提供了一种方法来创建 post 消息的流,您可以观察到。如果您使用消息的速度比生成消息的速度慢,它们也会缓冲消息。
一段时间后我才再次注意到这个问题,我最终做到的方法是将适配器保留在视图模型中,这样代码就变成了
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
vm = ViewModelProviders.of(this).get(MainViewModel::class.java)
messages.adapter = vm.messageAdapter
setUpObservers()
}
然后在视图模型中
class MainViewModel : ViewModel() {
private val logic: MainLogic = MainLogic(this, AppDispatchers)
val messageAdapter = MessageAdapter()
fun addMessage(message: String) {
messageAdapter.add(message)
}
override fun clearMessages() {
messageAdapter.clear()
}
}
当连接适配器时,所有的通知事件都会触发(因为它可以找到 RecyclerView),当它不连接时它什么也不做。
重要的似乎是不要向适配器添加任何类型的上下文相关变量。
在真正需要它的唯一方法 (onCreateViewHolder) 上,您可以从 parent.context 获取它,它仅在连接适配器时存在
在连接到蓝牙设备时,我从中收到大量测量结果,我希望在屏幕上显示这些测量结果而不重新发布整个列表。
为此,我使用了一个 ViewModel,如下所示:
class MainViewModel : ViewModel() {
private val logic: MainLogic = MainLogic(this, AppDispatchers)
val messageList = mutableListOf<String>()
val newMessage = SingleLiveEvent<String>()
val clearMessages = SingleLiveEvent<Unit>()
fun addMessage(message: String) {
messageList.add(message)
newMessage.postValue(message)
}
override fun clearMessages() {
messageList.clear()
clearMessages.postValue(null)
}
}
它在视图模型中包含完整列表,应该只将更新发送到主屏幕
我也使用 SingleLiveEvent,如下所示:
我的主要 activity 看起来像这样:
class MainActivity : AppCompatActivity() {
private lateinit var vm: MainViewModel
private lateinit var adapter: MessageAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
vm = ViewModelProviders.of(this).get(MainViewModel::class.java)
adapter = MessageAdapter(vm.messageList)
messages.adapter = adapter
setUpObservers()
}
private fun setUpObservers() {
vm.clearMessages.observe(this, Observer {
adapter.clear()
})
vm.newMessage.observe(this, Observer {
adapter.addMessage(it)
})
}
}
当消息流很慢时,这会正常工作,适配器只会添加新消息,当收到要清除的消息时,它会被清除。
不幸的是,当消息流很快时,很多消息都丢失了,当我旋转屏幕时(因此适配器是从 vm.messageList
重新创建的)所有消息都会显示,所以我没有丢失任何东西
有没有办法在不重新发布整个列表的情况下正确显示列表?
可能的解决方法是将此新消息与以前的消息一起存储在本地数据库(如 Room)中,并按超时更新列表(例如每 2 秒)and/or 按关键消息差异(数据库中的消息数量减去适配器中的消息数量 > 某个阈值)。
要加快从数据库下载消息的速度,您可以使用 Android 架构组件中的页面库。 请参阅:https://developer.android.com/reference/android/arch/paging/PagedList 和 https://developer.android.com/reference/android/support/v7/util/DiffUtil
希望对您有所帮助
这里的问题是您使用的 SingleLiveEvent
一次举办一个活动。因此,如果您连续多次调用 newMessage.postValue(message)
,新消息会覆盖尚未发送的前一条消息。
绝对可以在不重新加载整个数据集的情况下更新列表。假设您正在使用 RecylerView
并调用 notifyItemInserted
properly, you just need to pass all the new messages to your activity. There are quite a few ways to do so, e.g. use rxJava or kotlin coroutines channels。它们都提供了一种方法来创建 post 消息的流,您可以观察到。如果您使用消息的速度比生成消息的速度慢,它们也会缓冲消息。
一段时间后我才再次注意到这个问题,我最终做到的方法是将适配器保留在视图模型中,这样代码就变成了
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
vm = ViewModelProviders.of(this).get(MainViewModel::class.java)
messages.adapter = vm.messageAdapter
setUpObservers()
}
然后在视图模型中
class MainViewModel : ViewModel() {
private val logic: MainLogic = MainLogic(this, AppDispatchers)
val messageAdapter = MessageAdapter()
fun addMessage(message: String) {
messageAdapter.add(message)
}
override fun clearMessages() {
messageAdapter.clear()
}
}
当连接适配器时,所有的通知事件都会触发(因为它可以找到 RecyclerView),当它不连接时它什么也不做。
重要的似乎是不要向适配器添加任何类型的上下文相关变量。
在真正需要它的唯一方法 (onCreateViewHolder) 上,您可以从 parent.context 获取它,它仅在连接适配器时存在