3

我正在尝试新的协程流程,我的目标是制作一个简单的存储库,可以从 web api 获取数据并将其保存到数据库,也可以从数据库返回一个流程。

我使用 room 和 firebase 作为 web api,现在一切看起来都很简单,直到我尝试将来自 api 的错误传递给 ui。

由于我从仅包含数据且不包含状态的数据库中获得了一个流,因此通过将其与 Web api 结果相结合来为其赋予状态(如加载、内容、错误)的正确方法是什么?

我写的一些代码:

道:

@Query("SELECT * FROM users")
fun getUsers(): Flow<List<UserPojo>>

存储库:

val users: Flow<List<UserPojo>> = userDao.getUsers()

Api 调用:

override fun downloadUsers(filters: UserListFilters, onResult: (result: FailableWrapper<MutableList<UserApiPojo>>) -> Unit) {
    val data = Gson().toJson(filters)

    functions.getHttpsCallable("users").call(data).addOnSuccessListener {
        try {
            val type = object : TypeToken<List<UserApiPojo>>() {}.type
            val users = Gson().fromJson<List<UserApiPojo>>(it.data.toString(), type)
            onResult.invoke(FailableWrapper(users.toMutableList(), null))
        } catch (e: java.lang.Exception) {
            onResult.invoke(FailableWrapper(null, "Error parsing data"))
        }
    }.addOnFailureListener {
        onResult(FailableWrapper(null, it.localizedMessage))
    }
}

我希望问题足够清楚感谢您的帮助

编辑:由于问题不清楚,我会尽力澄清。我的问题是房间发出的默认流只有数据,所以如果我要订阅流,我只会收到数据(例如,在这种情况下,我只会收到用户列表)。我需要实现的是某种通知应用程序状态的方法,例如加载或错误。目前我能想到的唯一方法是包含状态的“响应”对象,但我似乎找不到实现它的方法。

就像是:

fun getUsers(): Flow<Lce<List<UserPojo>>>{
    emit(Loading())
    downloadFromApi()
    if(downloadSuccessful)
        return flowFromDatabase
    else
        emit(Error(throwable))
}

但是我遇到的一个明显问题是来自数据库的流是类型的Flow<List<UserPojo>>,我不知道如何通过编辑流的状态来“丰富它”,而不会丢失来自数据库的订阅,也不会运行新的每次更新数据库时的网络调用(通过在地图转换中进行)。

希望它更清楚

4

2 回答 2

5

我相信这更像是一个架构问题,但让我先尝试回答您的一些问题。

我的问题是房间发出的默认流只有数据,所以如果我要订阅流,我只会收到数据

如果 Room 返回有错误Flow,可以通过catch()

我需要实现的是某种通知应用程序状态的方法,例如加载或错误。

我同意你的观点,拥有一个State对象是一个好方法。在我看来,ViewModelState对象呈现给View. 这个State对象应该有办法暴露错误。

目前我能想到的唯一方法是包含状态的“响应”对象,但我似乎找不到实现它的方法。

我发现让控件负责错误的对象而不是从层冒出的State对象更容易。ViewModelService

现在解决了这些问题,让我尝试为您的问题提出一个特定的“解决方案”。

正如您所提到的,通常的做法Repository是处理从多个数据源中检索数据。在这种情况下,Repository将采用DAO和 一个表示从网络获取数据的对象,我们称之为Api。我假设您正在使用FirebaseFirestore,因此类和方法签名看起来像这样:

class Api(private val firestore: FirebaseFirestore) {

fun getUsers() : Flow<List<UserApiPojo>

}

现在问题变成了如何将基于回调的 API 转换为Flow. 幸运的是,我们可以使用callbackFlow()它。然后Api变成:

class Api(private val firestore: FirebaseFirestore) {

fun getUsers() : Flow<List<UserApiPojo> = callbackFlow {

val data = Gson().toJson(filters)

functions.getHttpsCallable("users").call(data).addOnSuccessListener {
    try {
        val type = object : TypeToken<List<UserApiPojo>>() {}.type
        val users = Gson().fromJson<List<UserApiPojo>>(it.data.toString(), type)
        offer(users.toMutableList())
    } catch (e: java.lang.Exception) {
       cancel(CancellationException("API Error", e))
    }
}.addOnFailureListener {
    cancel(CancellationException("Failure", e))
    }
  }
}

如您所见,callbackFlow允许我们在出现问题时取消流程并让下游人员处理错误。

移动到Repository我们现在想做类似的事情:

val users: Flow<List<User>> = Flow.concat(userDao.getUsers().toUsers(), api.getUsers().toUsers()).first()

这里有一些警告。first()并且concat()是运营商,你将不得不想出它似乎。我没有看到first()返回 a的版本Flow;它是一个终端操作符(Rx 曾经有一个first()返回的版本Observable,Dan Lew 在这篇文章中使用它)。Flow.concat()似乎也不存在。的目标users是返回一个Flow发出任何源发出的第一个值的a Flows。另外,请注意,我将 DAO 用户和 Api 用户映射到一个公共User对象。

我们现在可以谈谈ViewModel. 正如我之前所说,ViewModel应该有一些东西State。这State应该代表数据、错误和加载状态。可以完成的一种方法是使用数据类。

data class State(val users: List<User>, val loading: Boolean, val serverError: Boolean)

Repository由于我们可以访问ViewModelcan 看起来像:

val state = repo.users.map {users -> State(users, false, false)}.catch {emit(State(emptyList(), false, true)}

请记住,这是为您指明方向的粗略解释,有很多方法可以完成状态管理,这绝不是一个完整的实现。例如,将 API 调用转换Flow为 .

于 2020-01-29T17:47:53.103 回答
0

伊曼纽尔的回答非常接近于回答我的需要,我需要对其中一些进行澄清。

将 API 调用转换为 Flow 甚至可能没有意义

你是完全正确的,事实上我只是想让它成为一个协程,我真的不需要它成为一个流程。

如果 Room 返回的 Flow 有错误,可以通过 catch() 处理

是的,我在发布问题后发现了这一点。但我的问题更像是:

我想调用一个方法,比如“getData”,这个方法应该从数据库返回流,启动网络调用来更新数据库(这样当它通过数据库流完成时我会收到通知)和在这里的某个地方,我需要让用户界面知道数据库或网络是否出错,对吗?或者我应该做一个单独的“getDbFlow”和“updateData”并分别为每个错误获取错误?

val 用户:Flow> = Flow.concat(userDao.getUsers().toUsers(), api.getUsers().toUsers()).first()

这是一个好主意,但我想将数据库作为唯一的事实来源,并且永远不要直接从网络返回任何数据到 ui

于 2020-01-30T17:03:08.767 回答