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

Gav*_*ght 5 android coroutine kotlin kotlin-flow

我刚开始使用Flows. 我遇到的情况是,我需要在调用之前等待userLoginStatusChangedFlow收集readProfileFromFirestore()(这也会收集Flow)。第一个检查用户是否已登录 Firebase Auth,而第二个则从 Firestore 下载用户的个人资料信息。我的代码可以工作,但我不确定我是否按照预期的方式进行操作。

Flows问:这样“连锁”是标准做法吗?你会采取不同的做法吗?

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

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
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

Ten*_*r04 4

您的代码有一些不寻常的地方。

首先,具有单一值的 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) {
        //...
    }
}
Run Code Online (Sandbox Code Playgroud)

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