Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MediatorLiveData or switchMap transformation with multiple parameters

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.

like image 367
Yann39 Avatar asked Mar 26 '18 14:03

Yann39


2 Answers

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) } 
like image 53
jL4 Avatar answered Sep 21 '22 10:09

jL4


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     }); } 
like image 35
Yann39 Avatar answered Sep 19 '22 10:09

Yann39