我想构建一个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,它不会被取消。但是后来我失去了生命周期处理(启动后开始订阅,销毁后取消)。
那么,如何保持通道打开并仍然处理生命周期?还有更好的方法吗?
答案 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
。
答案 1 :(得分:0)
我使用了也提到的答案,然后我意识到当用户快速浏览应用程序时它会记住之前的事件。我的解决方案是这样的:
private val _effect = Channel<CurrenciesEffect>(1)
val effect = _effect.receiveAsFlow().conflate()
在片段中
private fun observeEffect() = lifecycleScope.launchWhenStarted {
currenciesViewModel.effect.collect { viewEffect ->
...
}
}