How to combine two live data one after the other?

半腔热情 提交于 2019-12-04 18:38:00

问题


I have next use case: User comes to registration form, enters name, email and password and clicks on register button. After that system needs to check if email is taken or not and based on that show error message or create new user...

I am trying to do that using Room, ViewModel and LiveData. This is some project that on which I try to learn these components and I do not have remote api, I will store everything in local database

So I have these classes:

  • RegisterActivity
  • RegisterViewModel
  • User
  • UsersDAO
  • UsersRepository
  • UsersRegistrationService

So the idea that I have is that there will be listener attached to register button which will call RegisterViewModel::register() method.

class RegisterViewModel extends ViewModel {

    //...

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

    //...

}

So that is the basic idea, I also want for performRegistration to return to me newly created user.

The thing that bothers me the most is I do not know how to implement performRegistration function in the service

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

As I understand, methods that are in UsersRepository should return LiveData because UsersDAO is returning 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);
    }
}

So my problem is how to implement performRegistration() function and how to pass value back to view model and then how to change activity from RegisterActivity to MainActivity...


回答1:


With the help of MediatorLiveData, you can combine results from multiple sources. Here an example of how would I combine two sources:

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

here is the gist for above, in case it is updated on the future: https://gist.github.com/guness/0a96d80bc1fb969fa70a5448aa34c215




回答2:


You can use my helper method:

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
}



回答3:


Jose Alcérreca has probably the 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
}



回答4:


You can define a method that would combine multiple LiveDatas using a MediatorLiveData, then expose this combined result as a tuple.

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

If you need more values, then you can create a CombinedLiveData3<A,B,C> and expose a Triple<A,B,C> instead of the Pair, etc.




回答5:


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

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


来源:https://stackoverflow.com/questions/50599830/how-to-combine-two-live-data-one-after-the-other

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!