Преобразование MediatorLiveData или switchMap с несколькими параметрами

Я использую Transformations.switchMap в своей модели просмотра, поэтому моя LiveData коллекция в моем фрагменте, реагирует на изменение параметра code.

Это отлично работает:

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

Проблема

Теперь мне нужен еще один параметр (nbDays прокомментирован в приведенном выше коде), чтобы мой объект LiveData реагировал на изменение обоих параметров (code и nbDays).

Как я могу связать преобразования?

Некоторые читатели указали мне на MediatorLiveData, но это не решает мою проблему. проблема (все еще нужно вызвать одну функцию БД с 2 параметрами, мне не нужно объединять 2 liveDatas).

Поэтому я попробовал это вместо switchMap, но code и nbDays всегда равны нулю.

dayPrices.addSource(
    dbManager.getDayPriceData(code.getValue(), nbDays.getValue),
    apiResponse -> dayPrices.setValue(apiResponse)
);

Одним из решений было бы передать объект как единственный параметр, я уверен, что есть простое решение для этого.




Ответы (4)


Источник: https://plus.google.com/+MichielPijnackerHordijk/posts/QGXF9gRomVi

Чтобы иметь несколько триггеров для switchMap(), вам необходимо использовать пользовательский MediatorLiveData для наблюдения за комбинацией объектов LiveData -

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

Тогда ты сможешь сделать это -

CustomLiveData trigger = new CustomLiveData(code, nbDays);
LiveData<DayPrices> dayPrices = Transformations.switchMap(trigger, 
    value -> dbManager.getDayPriceData(value.first, value.second));

Если вы используете Kotlin и хотите работать с дженериками:

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

Потом:

val dayPrices = Transformations.switchMap(DoubleTrigger(code, nbDays)) {
    dbManager.getDayPriceData(it.first, it.second)
}
person jL4    schedule 30.03.2018
comment
Спасибо, очень помогли! Но в этом случае switchMap запускается два раза подряд, можно ли в этом случае пропустить 1-й источник? - person aleksandrbel; 12.04.2019
comment
@aleksandrbel Вы имеете в виду, что switchMap можно запускать дважды, используя отдельные сеттеры для code и nbDays? Тогда да, это может привести к тому, что наблюдатель получит два подряд идущих значения. Если вы не хотите такого поведения, было бы лучше использовать только один объект Pair в MutableLiveData, который объединяет оба значения, как в ответе OP ниже. - person jL4; 13.08.2019

Пользовательский MediatorLiveData, предложенный @ jL4, отлично работает и, вероятно, является решением.

Я просто хотел поделиться самым простым решением, которое, как мне кажется, заключается в использовании внутреннего класса для представления составленных значений фильтра:

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

}

Затем в активности / фрагменте:

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
    });
}
person Yann39    schedule 16.04.2018
comment
Вы можете реализовать equals в DayPriceFilter. - person aeracode; 08.01.2021

Упрощение ответа jL4 (а также в Kotlin, если это кому-то помогает) ... нет необходимости создавать для этого собственный класс:

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
    }

}

Объяснение

Любое обновление в firstLiveData или secondLiveData обновит значение combinedValues и выдаст два значения как пару (за это спасибо jL4).

Вызов liveData.value может иметь значение NULL, поэтому это решение делает значения в Pair допускающими значение NULL, чтобы избежать исключения Null Pointer Exception.

Таким образом, для фактического вызова результатов / источника данных карта переключателя находится в combinedValues живых данных, и 2 значения извлекаются из Pair, и выполняются нулевые проверки, поэтому вы можете быть уверены в передаче ненулевых значений в ваш источник данных.

person Vin Norman    schedule 24.03.2020
comment
Это очень хорошее решение. Есть ли способ расширить его с помощью N источников ?? У меня есть случай, что количество источников неизвестно ..! - person james04; 16.09.2020

Столкнулся с похожей проблемой. Есть 2 способа решить эту проблему:

  1. Либо используйте MediatorLiveData
  2. Используйте RxJava, поскольку в нем есть различные операторы для выполнения таких сложных вещей.

Если вы не знаете RxJava, я бы порекомендовал написать свой собственный класс MediatorLiveData. Чтобы узнать, как написать собственный класс MediatorLiveData, просмотрите этот пример: https://gist.github.com/AkshayChordiya/a79bfcc422fd27d52b15cdafc55eac6b

person Akshay Chordiya    schedule 29.03.2018
comment
Как я могу выполнить сетевой вызов, для которого требуется 4 LiveData (например, userId, emial, apiKey и т. Д.). Transformation.switchMap принимает только один параметр LiveData Пожалуйста, дайте несколько предложений. ТГц - person amlwin; 29.07.2018
comment
Я столкнулся с аналогичной ситуацией и использовал MediatorLiveData. Я бы порекомендовал его и посмотрел, подходит ли он вашему варианту использования, иначе вы можете использовать RxJava, а затем преобразовать его в LiveData. - person Akshay Chordiya; 29.07.2018
comment
если не возражаете, не могли бы вы поделиться фрагментом кода? - person amlwin; 30.07.2018
comment
мертвая ссылка к сожалению - person Richard Le Mesurier; 10.12.2018
comment
@RichardLeMesurier Он продолжает становиться недействительным, потому что в репозитории происходят постоянные изменения. Поэтому теперь я добавил ссылку на свою суть, чтобы она не ломалась - person Akshay Chordiya; 11.12.2018
comment
Обидно, да. Я также только что разместил ссылку на изображение для ответа, и я знаю, что скоро он тоже станет мертвым ... - person Richard Le Mesurier; 11.12.2018