Kotlin SharedFlow ViewModel 在订阅之前发出
Kotlin SharedFlow ViewModel emits before subscribed
我正在尝试使用 SharedFlow 作为 MVVM 架构中片段的数据提供者。
片段中class:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.data.collect { value ->
handleData(data)
}
}
}
viewModel.init()
}
在ViewModel中class:
private val _data: MutableSharedFlow<DataState> = MutableSharedFlow()
val data: SharedFlow<DataState> = _data
fun init() {
...
//(listen for other data providers that generate data for SharedFlow)
...
viewModelCoroutineScope.launch {
val dataCollection = interactor.getDataCollection()
dataCollection.forEach { data ->
if (data != null) {
_data.emit(DataState(data = data))
}
}
}
}
问题是在 50% 的情况下 viewmodel.init()
在范围内的订阅者连接到 Flow 之前启动 - 这会导致一些数据丢失。
为什么使用 SharedFlow?这是因为 ViewModel 订阅了其他数据源,这些数据源可能会以不规则的方式发送大量需要收集的数据实例,因此 StateFlow/LiveData 的“仅存储最后一个值”对此不利。
当我尝试像这样将 viewmodel.init()
固定到订阅者协程时:
val job = viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.data.collect { value ->
handleData(data)
}
}
}
viewLifecycleOwner.lifecycleScope.launch {
job.join()
viewModel.init()
}
ViewModel 发出数据,但 Fragment 永远不会收集数据。
在调用 ViewModel 开始通过 SharedFlow 发送数据之前保证订阅者处于开启状态的正确方法是什么?
您应该为您的 SharedFlow 设置一个 replay
值 1,这样迟到的订阅者仍将获得最新的值。反正你需要这个。如果屏幕旋转,重新创建的 Fragment 将需要最新的值才能显示在 UI.
private val _data: MutableSharedFlow<DataState> = MutableSharedFlow(replay = 1)
但实际上,使用shareIn
而不是MutableSharedFlow
会更好,因为那样你可以在没有活跃订阅者时暂停收集,这样你就可以避免不必要的资源监控。关联的片段是 off-screen。像这样:
val data: SharedFlow<DataState> = interactor.getDataCollection()
.mapNotNull { it?.let(::DataState) }
.shareIn(viewModelScope, SharingStarted.whileSubscribed(5000L), replay = 1)
如果getDataCollection()
是一个挂起函数,你可以这样做:
val data: SharedFlow<DataState> = flow {
interactor.getDataCollection().emitAll()
}
.mapNotNull { it?.let(::DataState) }
.shareIn(viewModelScope, SharingStarted.whileSubscribed(5000L), replay = 1)
如果它不是暂停功能,为什么你有一个 getter 功能? Kotlin 改用属性。
我正在尝试使用 SharedFlow 作为 MVVM 架构中片段的数据提供者。
片段中class:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.data.collect { value ->
handleData(data)
}
}
}
viewModel.init()
}
在ViewModel中class:
private val _data: MutableSharedFlow<DataState> = MutableSharedFlow()
val data: SharedFlow<DataState> = _data
fun init() {
...
//(listen for other data providers that generate data for SharedFlow)
...
viewModelCoroutineScope.launch {
val dataCollection = interactor.getDataCollection()
dataCollection.forEach { data ->
if (data != null) {
_data.emit(DataState(data = data))
}
}
}
}
问题是在 50% 的情况下 viewmodel.init()
在范围内的订阅者连接到 Flow 之前启动 - 这会导致一些数据丢失。
为什么使用 SharedFlow?这是因为 ViewModel 订阅了其他数据源,这些数据源可能会以不规则的方式发送大量需要收集的数据实例,因此 StateFlow/LiveData 的“仅存储最后一个值”对此不利。
当我尝试像这样将 viewmodel.init()
固定到订阅者协程时:
val job = viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.data.collect { value ->
handleData(data)
}
}
}
viewLifecycleOwner.lifecycleScope.launch {
job.join()
viewModel.init()
}
ViewModel 发出数据,但 Fragment 永远不会收集数据。
在调用 ViewModel 开始通过 SharedFlow 发送数据之前保证订阅者处于开启状态的正确方法是什么?
您应该为您的 SharedFlow 设置一个 replay
值 1,这样迟到的订阅者仍将获得最新的值。反正你需要这个。如果屏幕旋转,重新创建的 Fragment 将需要最新的值才能显示在 UI.
private val _data: MutableSharedFlow<DataState> = MutableSharedFlow(replay = 1)
但实际上,使用shareIn
而不是MutableSharedFlow
会更好,因为那样你可以在没有活跃订阅者时暂停收集,这样你就可以避免不必要的资源监控。关联的片段是 off-screen。像这样:
val data: SharedFlow<DataState> = interactor.getDataCollection()
.mapNotNull { it?.let(::DataState) }
.shareIn(viewModelScope, SharingStarted.whileSubscribed(5000L), replay = 1)
如果getDataCollection()
是一个挂起函数,你可以这样做:
val data: SharedFlow<DataState> = flow {
interactor.getDataCollection().emitAll()
}
.mapNotNull { it?.let(::DataState) }
.shareIn(viewModelScope, SharingStarted.whileSubscribed(5000L), replay = 1)
如果它不是暂停功能,为什么你有一个 getter 功能? Kotlin 改用属性。