具有协程缓冲区的SingleLiveEvent

时间:2019-11-22 23:04:23

标签: android android-livedata kotlin-coroutines

我想构建一个SingleLiveEvent的替代品,其中有一个缓冲区,以避免在消耗新旧缓冲区之前出现新缓冲区丢失事件。
我们的目标是拥有一个发出事件的Android ActivityViewModel以及使用这些事件的几个片段。这意味着,当一个片段被销毁时,它将停止其预订;而当一个新的片段对那些事件感兴趣时,则应对其进行预订。换句话说,应该可以有0个,1个或更多的订户。

我正在尝试使用以下代码使用kotlin协程通道构建此代码。在发出事件的ActivityViewModel中:

val userEvent = Channel<UserEvent>(Channel.UNLIMITED)
suspend fun onUserEvent(event: UserEvent) {
    userEvent.send(event)
}

在应订阅的片段中:

override fun onCreateView(...) {
    (...)
    lifecycleScope.launchWhenStarted {
        sharedViewModel.userEvent.consumeEach {
            // do something with event
        }
    }

}

我面临的问题是片段会消耗这些事件,但是每当取消其作用域时,通道也会被取消。然后,另一个片段订阅,并且当ActivityViewModel尝试send时,它会因JobCancellationException为真而以userEvent.isClosedForSend崩溃。

如果我使用GlobalScope.launch,它不会被取消。但是后来我失去了生命周期处理(启动后开始订阅,销毁后取消)。

那么,如何保持通道打开并仍然处理生命周期?还有更好的方法吗?

2 个答案:

答案 0 :(得分:0)

使用Channel代替使用BroadcastChannel

ViewModel:

protected val eventSender = BroadcastChannel<UserEvent>(Channel.BUFFERED)
val eventReceiver = eventSender.asFlow()

suspend fun onUserEvent(event: UserEvent) {
    eventSender.send(event)
}

活动/片段:

override fun onCreateView(...) {
    (...)
    viewModel.eventReceiver.onEach {
        // do something with event
    }.launchIn(lifecycleScope)
}

使用BroadcastChannel + Flow的组合,当您的屏幕打开订阅时会创建一个新的ReceiveChannel,并且在视图被销毁时仅会关闭此ReceiveChannel

Detailed information can be found here

答案 1 :(得分:0)

我使用了也提到的答案,然后我意识到当用户快速浏览应用程序时它会记住之前的事件。我的解决方案是这样的:

    private val _effect = Channel<CurrenciesEffect>(1)
    val effect = _effect.receiveAsFlow().conflate()

在片段中

    private fun observeEffect() = lifecycleScope.launchWhenStarted {
        currenciesViewModel.effect.collect { viewEffect ->
            ...
        }
    }