Android LiveData - switchMap is not triggered on second update
B

2

7

I have a LiveData object that depends on another LiveData. As I understand, Transformations.switchMap should allow to chain them. But switchMap handler is triggered only once and it doesn't react on further updates. If instead I use observe on the first object and, when it's ready, retrieve the second, it works fine but in this case I have to do it in Activity rather than ViewModel. Is it possible to chain LiveData objects, like Transformations.switchMap, but receive all updates, not only the first one?

Here is an attempt to use switchMap:

LiveData<Resource<User>> userLiveData = usersRepository.get();
return Transformations.switchMap(userLiveData, resource -> {
    if (resource.status == Status.SUCCESS && resource.data != null) {
        return apiService.cartItems("Bearer " + resource.data.token);
    } else {
        return AbsentLiveData.create();
    }
});

Here is an approach with observe in activity (works but requires to keep logic in activity):

viewModel.user().observe(this, x -> {
    if (x != null && x.data != null) {
        viewModel.items(x.data.token).observe(this, result -> {
            // use result
        });
    }
});
Bladderwort answered 21/11, 2017 at 19:30 Comment(2)
Hi, did you find the solution?Coward
Hi, yes, posted it belowBladderwort
B
1

As a workaround, I used MediatorLiveData. I add the result of the first call as a source and, when it's ready, replace it with a final call:

MediatorLiveData<MyResponse> result = new MediatorLiveData<>();
LiveData<Resource<User>> source = this.get();
result.addSource(source, resource -> {
    if (resource.status == Status.SUCCESS && resource.data != null) {
        result.removeSource(source);
        result.addSource(apiService.cartItems("Bearer " + resource.data.token), result::postValue);
    }
});
return result;
Bladderwort answered 28/12, 2017 at 11:34 Comment(1)
I think it was ViewModel, but could be either way, depending on the app architectureBladderwort
H
3

I was trying to do something similar to you. I have a LiveData something, and when that changes I want to query somethingElse from the DB based on a property. Because the property can be null, if I query the DB with it, I'll get an exception. Therefore, if the property is null, I'm returning an empty MutableLiveData.

I have noticed, that when I returned this empty MutableLiveData, the observers that subscribed to somethingElse were not getting any updates. I saw that on your answer you ended up using a MediatorLiveData. Then I steped through my code with the debugger and noticed that the switchMap also uses a MediatorLiveData.

After experimenting a bit, I realised that when creating the empty MutableLiveData, it's initial value is null and won't trigger any updates. If I explicitly set the value, then it will notify the observers.

somethingElse = Transformations.switchMap(something, somethingObject -> {
                if (something.someProperty() != null) {
                    return repository.getSomethingElseByProperty(something.someProperty());
                }else{
                    MutableLiveData<SomethingElse> empty = new MutableLiveData<>();
                    empty.setValue(null);//need to set a value, to force update of observers
                    return empty;
                }

The code here has worked for me. In the question, you use an AbsentLiveData, which I don't know how it is implemented, so I'm not sure it would work exactly in that case.

Hypno answered 23/1, 2020 at 7:58 Comment(1)
Thank you so much, I had it above and for whatever reason it did not work until I updated my filter inside the switchmap function.Zondra
B
1

As a workaround, I used MediatorLiveData. I add the result of the first call as a source and, when it's ready, replace it with a final call:

MediatorLiveData<MyResponse> result = new MediatorLiveData<>();
LiveData<Resource<User>> source = this.get();
result.addSource(source, resource -> {
    if (resource.status == Status.SUCCESS && resource.data != null) {
        result.removeSource(source);
        result.addSource(apiService.cartItems("Bearer " + resource.data.token), result::postValue);
    }
});
return result;
Bladderwort answered 28/12, 2017 at 11:34 Comment(1)
I think it was ViewModel, but could be either way, depending on the app architectureBladderwort

© 2022 - 2024 — McMap. All rights reserved.