将流与一次性数据合并

Merging Flow with one shot data

我有一个 Flow 从数据库中获取项目(使用 Room 很容易),然后我有另一个数据源,它是一个 API。我可以称它为 API(他们没有实现 Flows,因为它是一个旧的 Java API),它会给我另一组项目(与数据库项目相同的类型)。

我的房间数据库 returns 已经有 Flow<List<MyItem>>,我可以显示它,它会更新 adding/deleting 个项目,但现在我需要从中获取另一组项目API 并且我不知道如何进行。此外,我只有在用户登录后才能获取它们(即,如果用户拥有有效令牌)。

所以,为了使用 combine,我使用函数 callbackFlow 将项目作为 Flow 获取,然后我将来自数据库的流和来自API。但是,我不知道如何在用户登录(获取项目)或注销(清除项目)时从 API 更新此流程。

在我的理想世界中,我会有一个具有会话令牌的 LiveData,如果它为空,我将清除流,否则我将向 API 提出请求,但我不知道如何实现它,我是 Flow 概念和组件的新手。

编辑:我尝试了以下方法,但我不知道我是否走在正确的道路上。

override fun getItemsAsFlow(): Flow<List<Item>>{
        return flow {
            val isLoggedIn = _loggedIn.value ?: false
            var id = 1000000L
            if (isLoggedIn) {
                val list = ItemAPIManager.getItems().map {
                    Item(id = id++, name = it.name)
                }
                emit(list)
            }
        }
    }

谁能帮帮我?谢谢。

更新:根据@Tenfour04 的回答,我更新了我的代码。我的 API 供应商这样做:

override fun getItemsAsFlow(): Flow<List<Item>> {
        val items =
        _loggedIn.flatMapLatest { loggedIn ->
            if(loggedIn) {
                callbackFlow {
                    var id = 1000000L
                    val list = ItemAPIManager.getItems().map {
                        Item(id = id++, name = it.name)
                    }
                    trySend(list)
                    awaitClose()
                }
            }
            else emptyFlow()
        }
        return items
    }

我的 ViewModel,其中所有这些合并创建一个 RecyclerView 具有此功能:

private val _databaseItems: Flow<List<Item>> = databaseProvider.getItemsAsFlow()
private val _otherDatabaseItems: Flow<List<Item>> = databaseProvider.getSpecialItemsAsFlow()
private val _apiItems: Flow<List<Item>> = apiProvider.getItemsAsFlow()

private fun fetchItems(): LiveData<List<ItemViewModel>> {
        val itemViewModelFlow = _databaseItems.combine<List<Item>, List<Item>, List<ItemViewModel>>(_otherDatabaseItems) { dbItems1, dbItems2 ->
            val retList = arrayListOf<ItemViewModel>()
            if (settingsProvider.areTheOtherItemsEnabled) {
                retList.addAll(createViewData(dbItems2))
            }
            retList.addAll(createViewData(dbItems1))
            return@combine retList
        }.combine<List<ItemViewModel>, List<Item>, List<ItemViewModel>>(_apiItems) { itemViewModels, apiItems ->
            val retList = arrayListOf<ItemViewModel>()
            retList.addAll(itemViewModels)
            retList.addAll(createViewData(apiItems))
            return@combine retList
        }

        return itemViewModelFlow.asLiveData()
    }

我有两个数据库列表这一事实与这里无关,我们可以将它们视为一个。基本上,如果启用了设置,这些将显示给用户,否则不会显示。

createViewData 函数只是将项目列表转换为 RecyclerView 中使用的项目视图模型列表。

现在,如果我在启动应用程序时登录,我可以看到两个数据库项目列表,我可以看到 API 项目,但如果我注销,我仍然可以看到 API 项。此外,如果我注销,我什至看不到数据库项目(我应该看到)。

感谢您的耐心等待,我还在学习中Flows

您的编辑尝试不会在登录状态更改时更改行为,除非您取消并重新收集它。

我在 phone 上,所以我可能会犯语法错误,但这会按照您的描述进行。我真的不明白你对数据库中的项目做了什么(比如它们是否是以前获取的 API 项目的缓存,或者它们是否是持久性补充项目)所以我把它忘了。

// In your API provider/manager class:

// update this when login state changes
private val isLoggedIn = MutableStateFlow(false)

// The callbackFlow that fetches API items
private val fetchedItems: Flow<List<MyItem>> = TODO()

// Flow that starts collecting and emitting from fetchedItems at
// login and emits an empty list when logout occurs. 
val loggedInItems = isLoggedIn.flatMapLatest { loggedIn ->
    if (loggedIn) fetchedItems else flowOf(emptyList()) 
}

如果回调流程依赖于登录提供的凭据,那么 isLoggedIn 流程可能会扩展为使用 class 将登录状态与这些凭据包装起来,并且回调流程可能会更多在 flatMapLatest 流中自然定义,因此它可以在这些凭据可用时使用它们。像这样:

sealed interface LoginState {
    object LoggedOut: LoginState
    data class LoggedIn(val token: String): LoginState
}

private val loginState = MutableStateFlow<LoginState>(LoggedOut)

val loggedInItems = loginState.flatMapLatest { loginState ->
    when (loginState) {
        LoggedOut -> flowOf(emptyList())
        is LoggedIn -> callbackFlow {
            val token = loginState.token
            //...
        }
    }        
}