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

时间:2018-05-30 08:26:21

标签: android observer-pattern android-room android-architecture-components android-livedata

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

我正在尝试使用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());
    }

    //...

}

所以这是基本的想法,我也想让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)
    }
}

据我所知,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);
    }
}

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

9 个答案:

答案 0 :(得分:2)

我做了一个基于@guness答案的方法。我发现限制为两个LiveData不好。如果要使用3怎么办?我们需要为每种情况创建不同的类。因此,我创建了一个处理无限数量LiveData的类。

/**
  * 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)
             }
         }
     }
 }

答案 1 :(得分:1)

如果您希望两个值都不为空

fun <T, V, R> LiveData<T>.combineWithNotNull(
        liveData: LiveData<V>,
        block: (T, V) -> R
): LiveData<R> {
    val result = MediatorLiveData<R>()
    result.addSource(this) {
        this.value?.let { first ->
            liveData.value?.let { second ->
                result.value = block(first, second)
            }
        }
    }
    result.addSource(liveData) {
        this.value?.let { first ->
            liveData.value?.let { second ->
                result.value = block(first, second)
            }
        }
    }

    return result
}

答案 2 :(得分:0)

JoseAlcérreca可能有best answer for this

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
}

答案 3 :(得分:0)

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

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
}

答案 4 :(得分:0)

您可以定义一个方法,该方法将使用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));
        });
    }
}

如果您需要更多值,则可以创建一个CombinedLiveData3<A,B,C>并公开一个Triple<A,B,C>来代替对,等等。

答案 5 :(得分:0)

LiveData liveData1 = ...;
 LiveData liveData2 = ...;

 MediatorLiveData liveDataMerger = new MediatorLiveData<>();
 liveDataMerger.addSource(liveData1, value -> liveDataMerger.setValue(value));
 liveDataMerger.addSource(liveData2, value -> liveDataMerger.setValue(value));

答案 6 :(得分:0)

如果您想在构建时创建字段和设置(使用 also):

val liveData1 = MutableLiveData(false)
val liveData2 = MutableLiveData(false)

// Return true if liveData1 && liveData2 are true
val liveDataCombined = MediatorLiveData<Boolean>().also {
    // Initial value
    it.value = false
    // Observing changes
    it.addSource(liveData1) { newValue ->
        it.value = newValue && liveData2.value!!
    }
    it.addSource(selectedAddOn) { newValue ->
        it.value = liveData1.value!! && newValue
    }
}

答案 7 :(得分:0)

没有自定义类

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

答案 8 :(得分:0)

其中许多答案都有效,但也假定 LiveData 泛型类型不可为空。

但是如果一个或多个给定的输入类型是可空类型(假设泛型的默认 Kotlin 上限是 Any?,它可以为空)怎么办? 结果是即使 LiveData 发射器会发出一个值 (null),MediatorLiveData 也会忽略它,认为这是他自己的子实时数据值没有被设置。

相反,这个解决方案通过强制传递给中介的类型的上限不为空来处理它。懒惰但需要。

此外,在调用组合器函数后,此实现避免了相同的值,这可能是您需要的,也可能不是您需要的,因此请随意删除那里的相等检查。

fun <T1 : Any, T2 : Any, R> combineLatest(
    liveData1: LiveData<T1>,
    liveData2: LiveData<T2>,
    combiner: (T1, T2) -> R,
): LiveData<R> = MediatorLiveData<R>().apply {
    var first: T1? = null
    var second: T2? = null

    fun updateValueIfNeeded() {
        value = combiner(
            first ?: return,
            second ?: return,
        )?.takeIf { it != value } ?: return
    }

    addSource(liveData1) {
        first = it
        updateValueIfNeeded()
    }
    addSource(liveData2) {
        second = it
        updateValueIfNeeded()
    }
}