带有房间和状态处理功能的Kotlin协程 [英] Kotlin Coroutines Flow with Room and state handling

查看:120
本文介绍了带有房间和状态处理功能的Kotlin协程的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试新协程的流程,我的目标是创建一个简单的存储库,该存储库可以从Web api获取数据并将其保存到db,还可以从db返回流.

I'm trying out the new coroutine's flow, my goal is to make a simple repository that can fetch data from a web api and save it to db, also return a flow from the db.

我正在使用room和firebase作为网络api,现在一切似乎都很简单,直到我尝试将api传递的错误传递给ui.

I'm using room and firebase as the web api, now everything seems pretty straight forward until i try to pass errors coming from the api to the ui.

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

Since i get a flow from the database which only contains the data and no state, what is the correct approach to give it a state (like loading, content, error) by combining it with the web api result?

我写的一些代码:

DAO:

@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))
    }
}

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

I hope the question is clear enough Thanks for the help

编辑:由于问题尚不清楚,因此我将尝试澄清.我的问题是,房间发出的默认流只有数据,因此,如果我订阅该流,我将仅接收数据(例如,在这种情况下,我将仅接收用户列表).我需要实现的是某种通知应用程序状态的方法,例如加载或错误.目前,我唯一想到的方法是一个包含状态的响应"对象,但是我似乎找不到实现它的方法.

Since the question wasn't clear i'll try to clarify. My issue is that with the default flow emitted by room you only have the data, so if i were to subscribe to the flow i would only receive the data (eg. In this case i would only receive a list of users). What i need to achieve is some way to notify the state of the app, like loading or error. At the moment the only way i can think of is a "response" object that contains the state, but i can't seem to find a way to implement it.

类似的东西:

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

但是我遇到的一个显而易见的问题是,数据库中的流的类型为Flow<List<UserPojo>>,我不知道如何通过编辑流的状态来丰富它",而又不会丢失来自该流的订阅.数据库,并且每次更新数据库时都不会运行新的网络调用(通过在映射转换中进行操作).

But the obvious issue i'm running into is that the flow from the database is of type Flow<List<UserPojo>>, i don't know how to "enrich it" with the state editing the flow, without losing the subscription from the database and without running a new network call every time the db is updated (by doing it in a map transformation).

希望更清楚

推荐答案

我认为这更多是关于体系结构的问题,但是让我先尝试回答您的一些问题.

I believe this is more of an architecture question, but let me try to answer some of your questions first.

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

My issue is that with the default flow emitted by room you only have the data, so if i were to subscribe to the flow i would only receive the data

如果Room返回的Flow出现错误,则可以通过

If there is an error with the Flow returned by Room, you can handle it via catch()

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

What i need to achieve is some way to notify the state of the app, like loading or error.

我同意您的观点,拥有State对象是一种很好的方法.在我看来,将State对象呈现给ViewViewModel的责任.这个State对象应该有一种暴露错误的方法.

I agree with you that having a State object is a good approach. In my mind, it is the ViewModel's responsibility to present the State object to the View. This State object should have a way to expose errors.

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

At the moment the only way i can think of is a "response" object that contains the state, but i can't seem to find a way to implement it.

我发现让State对象(由ViewModel控件负责)比起从Service层冒出来的对象要容易得多.

I have found that it is easier to have the State object that the ViewModel controls be responsible for errors instead of an object that bubbles up from the Service layer.

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

Now with these questions out of the way, let me try to propose one particular "solution" to your issue.

正如您提到的,Repository是处理来自多个数据源的数据的常见做法.在这种情况下,Repository将采用DAO和一个表示从网络获取数据的对象,我们将其称为Api.我假设您使用的是FirebaseFirestore,因此类和方法的签名应如下所示:

As you mention, it is common practice to have a Repository that handles retrieving data from multiple data sources. In this case, the Repository would take the DAO and an object that represents getting data from the network, let's call it Api. I am assuming that you are using FirebaseFirestore, so the class and method signature would look something like this:

class Api(private val firestore: FirebaseFirestore) {

fun getUsers() : Flow<List<UserApiPojo>

}

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

Now the question becomes how to turn a callback based API into a Flow. Luckily, we can use callbackFlow() for this. Then Api becomes:

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允许我们在出现问题时取消流程,并由donwnstream处理错误.

As you can see, callbackFlow allows us to cancel the flow when something goes wrong and have someone donwnstream handle the error.

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

Moving to the Repository we would now like to do something like:

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

这里有一些警告. first()concat()是您似乎必须想出的运算符.我没有看到返回Flowfirst()版本;它是终端操作员(Rx以前具有first()的版本返回了Observable,Dan Lew在

There are a few caveats here. first() and concat() are operators you will have to come up with it seems. I did not see a version of first() that returns a Flow; it is a terminal operator (Rx used to have a version of first() that returned an Observable, Dan Lew uses it in this post). Flow.concat() does not seem to exist either. The goal of users is to return a Flow that emits the first value emitted by any of the source Flows. Also, note that I am mapping DAO users and Api users to a common User object.

我们现在可以讨论ViewModel.如前所述,ViewModel应该具有保存State的内容. State应该代表数据,错误和加载状态.可以实现的一种方法是使用数据类.

We can now talk about the ViewModel. As I said before, the ViewModel should have something that holds State. This State should represent data, errors and loading states. One way that can be accomplished is with a data class.

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

由于我们可以访问Repository,因此ViewModel可能如下所示:

Since we have access to the Repository the ViewModel can look like:

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

请记住,这是一个粗略的解释,旨在为您指明方向.有许多方法可以完成状态管理,但这绝不是一个完整的实现.例如,将API调用转换为Flow甚至没有任何意义.

Please keep in mind that this is a rough explanation to point you in a direction, there are many ways to accomplish state management and this is by no means a complete implementation. It may not even make sense to turn the API call into a Flow, for example.

这篇关于带有房间和状态处理功能的Kotlin协程的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆