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 改用属性。