链接流(在另一个流的 collect{} 块中收集一个流)

Chaining Flows (collecting a Flow within the collect{} block of another Flow)

我刚开始使用 Flows。我有一种情况需要等待 userLoginStatusChangedFlow 收集,然后再调用 readProfileFromFirestore()(它也会收集 Flow)。第一个检查用户是否登录到 Firebase Auth,第二个从 Firestore 下载用户的个人资料信息。我的代码有效,但我不确定我是否以预期的方式进行。

问题:像这样“链接”Flows 是标准做法吗?你会有什么不同的做法吗?

    init {
        viewModelScope.launch {
            repository.userLoginStatusChangedFlow.collect { userLoggedIn: Boolean? ->
                if (userLoggedIn == true) {
                    launch {
                        readProfileFromFirestore()
                    }
                } else {
                    navigateToLoginFragment()
                }
            }
        }
    }

上面调用的readProfileFromFirestore()方法:

    // Download profile from Firestore and update the repository's cached profile.
    private suspend fun readProfileFromFirestore() {
        repository.readProfileFromFirestoreFlow().collect { state ->
            when (state) {
                is State.Success -> {
                    val profile: Models.Profile? = state.data
                    if (profile != null && repository.isProfileComplete(profile)) {
                        repository.updateCachedProfile(profile)
                        navigateToExploreFragment()
                    } else {
                        navigateToAuthenticationFragment()
                    }
                }
                is State.Failed -> {
                    // Error occurred while getting profile, so just inform user and go to LoginFragment.
                    displayErrorToast(state.throwable.message ?: "Failed to get profile")
                    navigateToAuthenticationFragment()
                }
                is State.Loading -> return@collect
            }
        }
    }

您的代码有几个不寻常的地方。

首先,您拥有单个值的 Flow 是不寻常的。 Flow 适用于在一段时间内到达的多个值。如果只有一项要检索,则挂起函数更为明智。您的两个流程似乎都有同样的奇怪行为。

其次,当这是协程需要做的最后一件事时,您正在从协程内部启动协程。没有理由为这种情况引入额外的复杂性。如果您需要在当前协程中还有更多想做的事情而不等待其他事件的情况下启动一些侧链操作,那么在协程中调用 launch 可能是有意义的。这里不是这种情况。

有时,需要具有有限数量项目的流,但更常见的是理论上无限的流,因为它们正在轮询某些正在进行的状态。在这些情况下,在收集后尝试在协程中执行任何操作会很奇怪,因为您不知道何时或是否会发生这种情况。

所以,看看你的代码,我会说你的流程一开始就不应该存在,你应该用挂起函数替换它们。

但如果由于某种原因你不能这样做(比如它可能是状态流的变化,你只想听听什么是最新的状态,对该状态做出反应并忽略未来的状态变化) ,那么你可以使用 first() 而不是 collect() 来使你的协程更简单,比如:

init {
    viewModelScope.launch {
        val userLoggedIn = repository.userLoginStatusChangedFlow.first()
        if (userLoggedIn == true) {
            readProfileFromFirestore()
        } else {
            navigateToLoginFragment()
        }
    }
}

private suspend fun readProfileFromFirestore() {
    val state = repository.readProfileFromFirestoreFlow().first()
    when (state) {
        //...
    }
}

请注意,此代码与将流替换为挂起函数时的代码非常相似。 first() 基本上将 Flow 转换为挂起函数。

首先,正如@Tenfour04 指出的那样,使用 first 运算符暂停函数就足够了,

如果你坚持使用流程(这对于提供用户交互式重试的情况可能是合理的),你可以将这两个流程与运算符flatMap-cluster(flatMapMerge / flatMapConcat / flatMapLatest)结合起来并使用单个 collect 来消耗 value

val  credentials = flow {  emit(true) }
val  profiles = flow { emit(State.Loading) }

credentials.flatMapMerge { value -> 
   if (!value) flow { emit(State.NotLogin) } else profiles
}.collect { state ->
   // mapping your state here
}