A Safe Emitter of Events
因此,满足第一个要求,很明显,一个流是必要的。LiveData或任何conflates Kotlin flow,如StateFlow或ConflatedBroadcastChannel,都不合适。一组快速发射的事件可能会相互覆盖,而只有最后一个事件被发射到观察者那里。
那么使用SharedFlow呢?这能帮助吗?不幸的是,不能。SharedFlow是热的。这意味着在没有观察者的时期,比如说在配置改变的时候,发射到流中的事件会被简单地丢弃。遗憾的是,这也使得SharedFlow不适合发射事件。
那么,我们有什么办法来满足第二和第三个要求呢?幸运的是,一些文章已经为我们描述过了。
JetBrains的Roman Elizarov写了一篇关于各种类型流量的不同使用情况的文章。
这篇文章中特别有趣的是 "A use-case for channels "一节,他描述了我们所需要的东西——一个单次事件总线,是一个缓冲的事件流。文章地址如下:https://elizarov.medium.com/shared-flows-broadcast-channels-899b675e805c
A Safe Observer of Events
Android Framework强加给开发者的不同的生命周期可能很难处理。许多操作只能在某些生命周期状态下安全地执行。例如,Fragment导航只能在onStart之后、onStop之前进行。
那么,我们如何安全地观察只在给定生命周期状态下的事件流呢?如果我们观察视图模型的事件流,比如说一个Fragment,在Fragment提供的coroutine范围内,这是否能满足我们的需要?
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.eventsFlow
.onEach {
when (it) {
is MainViewModel.Event.NavigateToSettings -> {}
is MainViewModel.Event.ShowSnackBar -> {}
is MainViewModel.Event.ShowToast -> {}
}
}
.launchIn(viewLifecycleOwner.lifecycleScope)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// get your view model here
lifecycleScope.launchWhenStarted {
viewModel.eventsFlow
.collect {
when (it) {
MainViewModel.Event.NavigateToSettings -> {}
is MainViewModel.Event.ShowSnackBar -> {}
is MainViewModel.Event.ShowToast -> {}
}
}
}
}
override fun onStart() {
super.onStart()
disposable = viewModel.eventsFlow
.asObservable() // converting to Rx for the example
.subscribe {
when (it) {
MainViewModel.Event.NavigateToSettings -> {}
is MainViewModel.Event.ShowSnackBar -> {}
is MainViewModel.Event.ShowToast -> {}
}
}
}
override fun onStop() {
super.onStop()
disposable?.dispose()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.eventsFlow
.onEach {
when (it) {
MainViewModel.Event.NavigateToSettings -> {}
is MainViewModel.Event.ShowSnackBar -> {}
is MainViewModel.Event.ShowToast -> {}
}
}
.observeInLifecycle(this)
}
// OR if you prefer a slightly tighter lifecycle observer:
// Be sure to use the right lifecycle owner in each spot.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.eventsFlow
.onEach {
when (it) {
MainViewModel.Event.NavigateToSettings -> {}
is MainViewModel.Event.ShowSnackBar -> {}
is MainViewModel.Event.ShowToast -> {}
}
}
.observeInLifecycle(viewLifecycleOwner)
}
Pulling It All Together
把所有的东西放在一起,这就是我用来定义 "单一现场事件 "流的基本模式,以及我如何安全地观察它。
总结一下:视图模型的事件流是用一个通道接收作为流来定义的。这允许视图模型提交事件而不必知道观察者的状态。在没有观察者的情况下,事件被缓冲了。
视图(即Fragment或Activity)只有在生命周期达到开始状态后才观察该流。当生命周期到达停止的事件时,观察就被取消了。这允许安全地处理事件,而不用担心Android生命周期带来的困难。
最后,在FlowObserver的帮助下,模板被消除了。
你可以在这里看到整个代码。
class MainViewModel : ViewModel() {
sealed class Event {
object NavigateToSettings: Event()
data class ShowSnackBar(val text: String): Event()
data class ShowToast(val text: String): Event()
}
private val eventChannel = Channel<Event>(Channel.BUFFERED)
val eventsFlow = eventChannel.receiveAsFlow()
init {
viewModelScope.launch {
eventChannel.send(Event.ShowSnackBar("Sample"))
eventChannel.send(Event.ShowToast("Toast"))
}
}
fun settingsButtonClicked() {
viewModelScope.launch {
eventChannel.send(Event.NavigateToSettings)
}
}
}
class MainFragment : Fragment() {
companion object {
fun newInstance() = MainFragment()
}
private val viewModel by viewModels<MainViewModel>()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
return inflater.inflate(R.layout.main_fragment, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Note that I've chosen to observe in the tighter view lifecycle here.
// This will potentially recreate an observer and cancel it as the
// fragment goes from onViewCreated through to onDestroyView and possibly
// back to onViewCreated. You may wish to use the "main" lifecycle owner
// instead. If that is the case you'll need to observe in onCreate with the
// correct lifecycle.
viewModel.eventsFlow
.onEach {
when (it) {
MainViewModel.Event.NavigateToSettings -> {}
is MainViewModel.Event.ShowSnackBar -> {}
is MainViewModel.Event.ShowToast -> {}
}
}
.observeInLifecycle(viewLifecycleOwner)
}
}
viewModel.events
.onEach {
// can get cancelled when the lifecycle state falls below min
}
.flowWithLifecycle(lifecycle = viewLifecycleOwner.lifecycle, minActiveState = Lifecycle.State.STARTED)
.onEach {
// Do things
}
.launchIn(viewLifecycleOwner.lifecycleScope)