使用 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,如下所示:

https://github.com/googlesamples/android-architecture/blob/dev-todo-mvvm-live/todoapp/app/src/main/java/com/example/android/architecture/blueprints/todoapp/SingleLiveEvent.java

我的主要 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/PagedListhttps://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 获取它,它仅在连接适配器时存在