如何一个接一个地组合两个实时数据?

clz*_*ola 10 android observer-pattern android-room android-livedata android-architecture-components

我有下一个用例:用户登录表格,输入姓名,电子邮件和密码,点击注册按钮.之后系统需要检查电子邮件是否被采用并基于该显示错误消息或创建新用户...

我试图使用Room,ViewModel和LiveData来做到这一点.这是我尝试学习这些组件的一些项目,我没有远程api,我会将所有内容存储在本地数据库中

所以我有这些课程:

  • RegisterActivity
  • RegisterViewModel
  • 用户
  • UsersDAO
  • UsersRepository
  • UsersRegistrationService

所以我的想法是将有一个监听器附加到注册按钮,它将调用RegisterViewModel::register()方法.

class RegisterViewModel extends ViewModel {

    //...

    public void register() {
        validationErrorMessage.setValue(null);
        if(!validateInput())
            return;
        registrationService.performRegistration(name.get(), email.get(), password.get());
    }

    //...

}
Run Code Online (Sandbox Code Playgroud)

所以这是基本的想法,我也想performRegistration回到我新建的用户.

最困扰我的是我不知道如何performRegistration在服务中实现功能

class UsersRegistrationService {
    private UsersRepository usersRepo;

    //...

    public LiveData<RegistrationResponse<Parent>>  performRegistration(String name, String email, String password) {
         // 1. check if email exists using repository
         // 2. if user exists return RegistrationResponse.error("Email is taken") 
         // 3. if user does not exists create new user and return RegistrationResponse(newUser)
    }
}
Run Code Online (Sandbox Code Playgroud)

据我了解,进入的方法UsersRepository应返回LiveData,因为UsersDAO返回LiveData

@Dao
abstract class UsersDAO { 
    @Query("SELECT * FROM users WHERE email = :email LIMIT 1")
    abstract LiveData<User> getUserByEmail(String email);
}

class UsersRepository {
    //...
    public LiveData<User> findUserByEmail(String email) {
        return this.usersDAO.getUserByEmail(email);
    }
}
Run Code Online (Sandbox Code Playgroud)

所以我的问题是如何实现performRegistration()函数以及如何将值传递回视图模型,然后如何将活动从RegisterActivity更改为MainActivity ...

gun*_*ess 13

在MediatorLiveData的帮助下,您可以组合来自多个来源的结果.这里有一个如何组合两个来源的例子:

class CombinedLiveData<T, K, S>(source1: LiveData<T>, source2: LiveData<K>, private val combine: (data1: T?, data2: K?) -> S) : MediatorLiveData<S>() {

private var data1: T? = null
private var data2: K? = null

init {
    super.addSource(source1) {
        data1 = it
        value = combine(data1, data2)
    }
    super.addSource(source2) {
        data2 = it
        value = combine(data1, data2)
    }
}

override fun <T : Any?> addSource(source: LiveData<T>, onChanged: Observer<T>) {
    throw UnsupportedOperationException()
}

override fun <T : Any?> removeSource(toRemote: LiveData<T>) {
    throw UnsupportedOperationException()
}
}
Run Code Online (Sandbox Code Playgroud)

这里是上面的要点,如果它在未来更新:https: //gist.github.com/guness/0a96d80bc1fb969fa70a5448aa34c215

  • 为了对此进行编译(至少在Android Studio 3.4下),我必须在addSource()的签名中的T类型参数之前添加“ in”。`override fun &lt;T:Any?&gt; addSource(source:LiveData &lt;T&gt;,onChanged:Observer &lt;in T&gt;){` (4认同)
  • 如果源计数是可变的而不是两个怎么办? (2认同)
  • 如何在Java中实现“组合”部分? (2认同)
  • @EpicPandaForce 你不需要覆盖,但它们只是防止误用 (2认同)

M-W*_*eEh 12

一种方法是为此使用流。

val profile = MutableLiveData<ProfileData>()
val user = MutableLiveData<CurrentUser>()

val titleFlow = profile.asFlow().combine(user.asFlow()){ profile, user ->
    "${profile.job} ${user.name}"
}
Run Code Online (Sandbox Code Playgroud)

然后是你的片段/活动:

viewLifecycleOwner.lifecycleScope.launch { 
    viewModel.titleFlow.collectLatest { title ->
        Log.d(">>", title)
    }
}
Run Code Online (Sandbox Code Playgroud)

这种方法的一个优点是,titleFlow只有当两个实时数据都发出至少一个值时才会发出值。这个交互式图表将帮助您理解这一点https://rxmarbles.com/#combineLatest

替代语法:

val titleFlow = combine(profile.asFlow(), user.asFlow()){ profile, user ->
    "${profile.job} ${user.name}"
}
Run Code Online (Sandbox Code Playgroud)


Dan*_*son 9

Jose Alcérreca 可能对此给出最好的答案

fun blogpostBoilerplateExample(newUser: String): LiveData<UserDataResult> {

    val liveData1 = userOnlineDataSource.getOnlineTime(newUser)
    val liveData2 = userCheckinsDataSource.getCheckins(newUser)

    val result = MediatorLiveData<UserDataResult>()

    result.addSource(liveData1) { value ->
        result.value = combineLatestData(liveData1, liveData2)
    }
    result.addSource(liveData2) { value ->
        result.value = combineLatestData(liveData1, liveData2)
    }
    return result
}
Run Code Online (Sandbox Code Playgroud)

  • 请注意,这不会为您合并结果。它只会在任一来源发生变化时通知您 (3认同)
  • @TheRealChx101合并数据的方法在链接的文章中,基本上是“combineLatestData”函数 (3认同)

Xan*_*Xan 9

没有自定义类

MediatorLiveData<Pair<Foo?, Bar?>>().apply {
    addSource(fooLiveData) { value = it to value?.second }
    addSource(barLiveData) { value = value?.first to it }
}.observe(this) { pair ->
    // TODO
}
Run Code Online (Sandbox Code Playgroud)


Mar*_*cki 6

您可以使用我的帮助方法:

val profile = MutableLiveData<ProfileData>()

val user = MutableLiveData<CurrentUser>()

val title = profile.combineWith(user) { profile, user ->
    "${profile.job} ${user.name}"
}

fun <T, K, R> LiveData<T>.combineWith(
    liveData: LiveData<K>,
    block: (T?, K?) -> R
): LiveData<R> {
    val result = MediatorLiveData<R>()
    result.addSource(this) {
        result.value = block.invoke(this.value, liveData.value)
    }
    result.addSource(liveData) {
        result.value = block.invoke(this.value, liveData.value)
    }
    return result
}
Run Code Online (Sandbox Code Playgroud)

  • 如此优雅和简洁,听起来很邪恶:) (15认同)
  • 美丽的 ... :) (4认同)

Epi*_*rce 5

您可以定义一个方法,使用 MediatorLiveData 组合多个 LiveData,然后将组合结果公开为元组。

public class CombinedLiveData2<A, B> extends MediatorLiveData<Pair<A, B>> {
    private A a;
    private B b;

    public CombinedLiveData2(LiveData<A> ld1, LiveData<B> ld2) {
        setValue(Pair.create(a, b));

        addSource(ld1, (a) -> { 
             if(a != null) {
                this.a = a;
             } 
             setValue(Pair.create(a, b)); 
        });

        addSource(ld2, (b) -> { 
            if(b != null) {
                this.b = b;
            } 
            setValue(Pair.create(a, b));
        });
    }
}
Run Code Online (Sandbox Code Playgroud)

如果您需要更多值,那么您可以创建 aCombinedLiveData3<A,B,C>并公开 aTriple<A,B,C>而不是 Pair 等。就像/sf/answers/3800507231/中一样。

编辑:嘿看,我什至为你制作了一个库,可以实现从 2 到 16 的数量:https://github.com/Zhuinden/livedata-combinetuple-kt

  • @AndroidDev123您不需要使用该类,请在编辑中使用该库:https://github.com/Zhuinden/livedata-combinetuple-kt 现在您可以说“combineTuple(liveData1, liveData2)”,它只会工作。Kotlin 库。 (2认同)

Dam*_*tes 5

我根据@guness 的回答采取了一种方法。我发现限制为两个LiveData并不好。如果我们想用3呢?我们需要为每种情况创建不同的类。因此,我创建了一个处理无限数量的LiveDatas 的类。

/**
  * CombinedLiveData is a helper class to combine results from multiple LiveData sources.
  * @param liveDatas Variable number of LiveData arguments.
  * @param combine   Function reference that will be used to combine all LiveData data results.
  * @param R         The type of data returned after combining all LiveData data.
  * Usage:
  * CombinedLiveData<SomeType>(
  *     getLiveData1(),
  *     getLiveData2(),
  *     ... ,
  *     getLiveDataN()
  * ) { datas: List<Any?> ->
  *     // Use datas[0], datas[1], ..., datas[N] to return a SomeType value
  * }
  */
 class CombinedLiveData<R>(vararg liveDatas: LiveData<*>,
                           private val combine: (datas: List<Any?>) -> R) : MediatorLiveData<R>() {

      private val datas: MutableList<Any?> = MutableList(liveDatas.size) { null }

      init {
         for(i in liveDatas.indices){
             super.addSource(liveDatas[i]) {
                 datas[i] = it
                 value = combine(datas)
             }
         }
     }
 }
Run Code Online (Sandbox Code Playgroud)