kotlin list替换
After the announcement of the StateFlow implementation this year, I got curious about the possibility to totally replace LiveData. This means one less project dependency and achieve a more independent code from Android framework.
宣布StateFlow之后 在今年的实施中,我对完全取代LiveData的可能性感到好奇。 这意味着更少的项目依赖关系,并获得了与Android框架更加独立的代码。
StateFlow is not the subject of this post but we can change the view states represented by a LiveData using a StateFlow. For SingleLiveEvent class, we need a different solution.
StateFlow不是本文的主题,但是我们可以使用StateFlow更改由LiveData表示的视图状态。 对于SingleLiveEvent类,我们需要其他解决方案。
SingleLiveEvent (SingleLiveEvent)
Following the MVVM pattern, ViewModel provides the view state and events/actions to the View. In the context of LiveData, the second could be implemented using the class SingleLiveEvent. Some examples of actions are: dialog show, snack bar display, screen navigation.
遵循MVVM模式,ViewModel向视图提供视图状态和事件/动作。 在LiveData上下文中,第二个可以使用类SingleLiveEvent实现。 动作示例包括:对话框显示,小吃店显示,屏幕导航。
第一种方法:渠道 (First approach: Channel)
Change from val action = SingleLiveEvent<Action>()
to val action = Channel<Action>(Channel.BUFFERED)
从val action = SingleLiveEvent<Action>()
更改为val action = Channel<Action>(Channel.BUFFERED)
and on the Activity side as simple as this:viewModel.action.onEach{ ... }.launchIn(lifecycleScope)
在“活动”这一边就这么简单: viewModel.action.onEach{ ... }.launchIn(lifecycleScope)
问题 (Problem)
Everything seemed to be working fine until I tested a configuration change that recreates my Activity. After that, the action is not executed anymore 😔
在我测试了重新创建Activity的配置更改之前,一切似乎都工作正常。 之后,将不再执行该动作executed
The Channel attached to the Activity lifecycle coroutine scope is canceled when Activity.onDestroy()
is called as a side effect of coroutine context cancellation.
当调用Activity.onDestroy()
作为协程上下文取消的副作用时,将取消附加到Activity生命周期协程范围的Channel 。
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
if (lifecycle.currentState <= Lifecycle.State.DESTROYED) {
lifecycle.removeObserver(this)
coroutineContext.cancel()
}
}
解(Solution)
Instead of using Channel, I changed to BroadcastChannel + Flow. Similar but different. Let's see!😉
我不使用Channel ,而是改为BroadcastChannel + Flow 。 相似但不同。 让我们来看看!😉
class MyViewModel : ViewModel() {
protected val actionSender = BroadcastChannel<Action>(Channel.BUFFERED)
val actionReceiver = actionSender.asFlow()
}
and observing it in the Activity:
并在“活动”中对其进行观察:
private fun observeActionCommand() {
lifecycleScope.launchWhenStarted {
viewModel.actionReceiver.collect {
// TODO
}
}
}
广播频道与频道(BroadcastChannel vs Channel)
BroadcastChannel is NOT a specialization of a Channel as the name would suggest. I even found Roman Elizarov comment about this:
顾名思义, BroadcastChannel不是对Channel的专门化。 我什至发现Roman Elizarov对此有评论:
Having thought about it a bit more, it looks the whole
BroadcastChannel
is a misnomer. They are not really channels! They are more like some kind of "hot flows".再多考虑一下,看起来整个
BroadcastChannel
都是错误的。 他们不是真正的渠道! 它们更像是某种“热流”。
Using the first approach with Channel, it implements SendChannel and ReceiveChannel that gets closed when the view lifecycle scope is cancelled.
通过将第一种方法与Channel结合使用,它实现了SendChannel和ReceiveChannel ,它们在取消视图生命周期范围时将关闭。
On the other hand, BroadcastChannel only implements SendChannel. A new ReceiveChannel is created to collect items from the BroadcastChanel (openSubscription) every time we launch the Flow (Flow from .asFlow). This way, only the ReceiveChannel is closed when the scope is cancelled and the BroadcastChannel remains opened.
另一方面, BroadcastChannel仅实现SendChannel。 每次我们启动流(来自.asFlow的流)时,都会创建一个新的ReceiveChannel来从BroadcastChanel(openSubscription)中收集项目。 这样,在取消作用域并且广播频道保持打开状态时,仅关闭接收频道。
Every flow collector will trigger a new broadcast channel subscription.
fun <T> BroadcastChannel<T>.asFlow()
每个流收集器将触发一个新的广播频道订阅。
fun <T> BroadcastChannel<T>.asFlow()
启动与启动时 (launch vs launchWhenStarted)
LiveData only emits when the LifecycleOwner is on active state (State.STARTED).
当LifecycleOwner是激活状态(State.STARTED)LiveData仅发射。
If we use launch on our solution, we may have the problematic scenario:
如果在解决方案上使用启动,则可能会遇到问题:
App in backgroundOur screen has gone to saved state but our action continues to be consumed. It may lead to an exception if we are trying to commit a fragment transaction for example.
后台应用屏幕已进入保存状态,但我们的操作仍在继续。 例如,如果我们尝试提交片段事务,可能会导致异常。
Using launchWhenStarted we achieve the same LiveData behaviour that pauses its consumption if the lifecycle state is "lower" than Started.
如果生命周期状态“低于” Started,则使用launchWhenStarted可以实现相同的LiveData行为,该行为将暂停其使用。
结论 (Conclusion)
I've written about a single LiveData use case exploring some common scenarios in which it may fail, improving our solution. LiveData is really useful and easy to work with Android, but we always need to consider and learn from other solutions.
我已经写了一个LiveData用例,探讨了一些可能失败的常见场景,从而改进了我们的解决方案。 LiveData确实非常有用,并且可以轻松地与Android一起使用,但是我们始终需要考虑其他解决方案并向其学习。
进一步阅读 (Further reading)
Kotlin: Diving in to Coroutines and ChannelsAmazing general article about Channels guided through a coffee shop analogy.
Kotlin:深入探讨协程和渠道关于咖啡店类比引导渠道的惊人的一般文章。
Cold flows, hot channelsDifferences between flow and channel.
冷流,热通道流与通道之间的差异。
翻译自: https://medium.com/@cesarmorigaki/replace-singleliveevent-with-kotlin-channel-flow-b983f095a47a
kotlin list替换