MediatorLiveData or switchMap transformation with multiple parameters

前端 未结 4 1172
我在风中等你
我在风中等你 2020-12-13 00:23

I am using Transformations.switchMap in my ViewModel so my LiveData collection, observed in my fragment, reacts on changes of code parameter.

相关标签:
4条回答
  • 2020-12-13 00:37

    I faced a similar problem. There are 2 ways to solve this:

    1. Either use MediatorLiveData
    2. Use RxJava as it has various operators to do such kind of complex stuff

    If you don't know RxJava, then I'd recommend writing your custom MediatorLiveData class. To learn how write custom MediatorLiveData class check out this example: https://gist.github.com/AkshayChordiya/a79bfcc422fd27d52b15cdafc55eac6b

    0 讨论(0)
  • 2020-12-13 00:40

    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)
    }
    
    0 讨论(0)
  • 2020-12-13 00:45

    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
        });
    }
    
    0 讨论(0)
  • 2020-12-13 01:01

    A simplification of jL4's answer, (and also in Kotlin in case it helps anybody)... no need to create a custom class for this:

    class YourViewModel: ViewModel() {
    
        val firstLiveData: LiveData<String> // or whatever type
        val secondLiveData: LiveData<Int> // or whatever
    
        // the Pair values are nullable as getting "liveData.value" can be null
        val combinedValues = MediatorLiveData<Pair<String?, Int?>>().apply {
            addSource(firstLiveData) { 
               value = Pair(it, secondLiveData.value)
            }
            addSource(secondLiveData) { 
               value = Pair(firstLiveData.value, it)
            }
        }
    
        val results = Transformations.switchMap(combinedValues) { pair ->
          val firstValue = pair.first
          val secondValue = pair.second
          if (firstValue != null && secondValue != null) {
             yourDataSource.yourLiveDataCall(firstValue, secondValue)
          } else null
        }
    
    }
    

    Explanation

    Any update in firstLiveData or secondLiveData will update the value of combinedValues, and emit the two values as a pair (thanks to jL4 for this).

    Calling liveData.value can be null, so this solution makes the values in Pair nullable to avoid Null Pointer Exception.

    So for the actual results/datasource call, the switch map is on the combinedValues live data, and the 2 values are extracted from the Pair and null checks are performed, so you can be sure of passing non-null values to your data source.

    0 讨论(0)
提交回复
热议问题