I am using Transformations.switchMap
in my ViewModel so my LiveData
collection, observed in my fragment, reacts on changes of code
parameter.
This works perfectly :
public class MyViewModel extends AndroidViewModel { private final LiveData<DayPrices> dayPrices; private final MutableLiveData<String> code = new MutableLiveData<>(); // private final MutableLiveData<Integer> nbDays = new MutableLiveData<>(); private final DBManager dbManager; public MyViewModel(Application application) { super(application); dbManager = new DBManager(application.getApplicationContext()); dayPrices = Transformations.switchMap( code, value -> dbManager.getDayPriceData(value/*, nbDays*/) ); } public LiveData<DayPrices> getDayPrices() { return dayPrices; } public void setCode(String code) { this.code.setValue(code); } /*public void setNbDays(int nbDays) { this.nbDays.setValue(nbDays); }*/ } public class MyFragment extends Fragment { private MyViewModel myViewModel; myViewModel = ViewModelProviders.of(this).get(MyViewModel.class); myViewModel.setCode("SO"); //myViewModel.setNbDays(30); myViewModel.getDayPrices().observe(MyFragment.this, dataList -> { // update UI with data from dataList }); }
Problem
I now need another parameter (nbDays
commented in the code above), so that my LiveData
object reacts on both parameters change (code
and nbDays
).
How can I chain transformations ?
Some reading pointed me to MediatorLiveData, but it does not solve my problem (still need to call single DB function with 2 parameters, I don't need to merge 2 liveDatas).
So I tried this instead of switchMap
but code
and nbDays
are always null.
dayPrices.addSource( dbManager.getDayPriceData(code.getValue(), nbDays.getValue), apiResponse -> dayPrices.setValue(apiResponse) );
One solution would be to pass an object as single parameter by I'm pretty sure there is a simple solution to this.
Source : https://plus.google.com/+MichielPijnackerHordijk/posts/QGXF9gRomVi
To have multiple triggers for switchMap()
, you need to use a custom MediatorLiveData
to observe the combination of the LiveData objects -
class CustomLiveData extends MediatorLiveData<Pair<String, Integer>> { public CustomLiveData(LiveData<String> code, LiveData<Integer> nbDays) { addSource(code, new Observer<String>() { public void onChanged(@Nullable String first) { setValue(Pair.create(first, nbDays.getValue())); } }); addSource(nbDays, new Observer<Integer>() { public void onChanged(@Nullable Integer second) { setValue(Pair.create(code.getValue(), second)); } }); } }
Then you can do this -
CustomLiveData trigger = new CustomLiveData(code, nbDays); LiveData<DayPrices> dayPrices = Transformations.switchMap(trigger, value -> dbManager.getDayPriceData(value.first, value.second));
If you use Kotlin and want to work with generics:
class DoubleTrigger<A, B>(a: LiveData<A>, b: LiveData<B>) : MediatorLiveData<Pair<A?, B?>>() { init { addSource(a) { value = it to b.value } addSource(b) { value = a.value to it } } }
Then:
val dayPrices = Transformations.switchMap(DoubleTrigger(code, nbDays)) { dbManager.getDayPriceData(it.first, it.second) }
Custom MediatorLiveData
as proposed by @jL4 works great and is probably the solution.
I just wanted to share the simplest solution that I think is to use an inner class to represent the composed filter values :
public class MyViewModel extends AndroidViewModel { private final LiveData<DayPrices> dayPrices; private final DBManager dbManager; private final MutableLiveData<DayPriceFilter> dayPriceFilter; public MyViewModel(Application application) { super(application); dbManager = new DBManager(application.getApplicationContext()); dayPriceFilter = new MutableLiveData<>(); dayPrices = Transformations.switchMap(dayPriceFilter, input -> dbManager.getDayPriceData(input.code, input.nbDays)); } public LiveData<DayPrices> getDayPrices() { return dayPrices; } public void setDayPriceFilter(String code, int nbDays) { DayPriceFilter update = new DayPriceFilter(code, nbDays); if (Objects.equals(dayPriceFilter.getValue(), update)) { return; } dayPriceFilter.setValue(update); } static class DayPriceFilter { final String code; final int nbDays; DayPriceFilter(String code, int nbDays) { this.code = code == null ? null : code.trim(); this.nbDays = nbDays; } } }
Then in the activity/fragment :
public class MyFragment extends Fragment { private MyViewModel myViewModel; myViewModel = ViewModelProviders.of(this).get(MyViewModel.class); myViewModel.setDayPriceFilter("SO", 365); myViewModel.getDayPrices().observe(MyFragment.this, dataList -> { // update UI with data from dataList }); }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With