web-dev-qa-db-fra.com

Transformation MediatorLiveData ou switchMap avec plusieurs paramètres

J'utilise Transformations.switchMap dans mon ViewModel pour que ma collection LiveData, observée dans mon fragment, réagisse aux changements du paramètre code.

Cela fonctionne parfaitement:

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

Problème

J'ai maintenant besoin d'un autre paramètre (nbDays commenté dans le code ci-dessus), afin que mon objet LiveData réagisse au changement des deux paramètres (code et nbDays).

Comment chaîner des transformations?

Certaines lectures m'ont montré MediatorLiveData , mais cela ne résout pas mon problème (j'ai toujours besoin d'appeler une seule fonction DB avec 2 paramètres, je n'ai pas besoin de fusionner 2 liveDatas).

J'ai donc essayé ceci au lieu de switchMap mais code et nbDays sont toujours nuls.

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

Une solution serait de passer un objet en paramètre unique par je suis presque sûr qu'il existe une solution simple à cela.

19
Yann39

Source: https://plus.google.com/+MichielPijnackerHordijk/posts/QGXF9gRomVi

Pour avoir plusieurs déclencheurs pour switchMap(), vous devez utiliser un MediatorLiveData personnalisé pour observer la combinaison des objets 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));
            }
        });
    }
}

Ensuite, vous pouvez le faire -

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

Si vous utilisez Kotlin et souhaitez travailler avec des génériques:

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

Ensuite:

val dayPrices = Transformations.switchMap(DoubleTrigger(code, nbDays)) {
    dbManager.getDayPriceData(it.first, it.second)
}
28
jL4

MediatorLiveData personnalisé comme proposé par @ jL4 fonctionne très bien et est probablement la solution.

Je voulais juste partager la solution la plus simple qui, selon moi, consiste à utiliser une classe interne pour représenter les valeurs de filtre composées:

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

}

Puis dans l'activité/le 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
    });
}
7
Yann39

J'ai fait face à un problème similaire. Il existe 2 façons de résoudre ce problème:

  1. Utilisez soit MediatorLiveData
  2. Utilisez RxJava car il a différents opérateurs pour faire ce genre de choses complexes

Si vous ne connaissez pas RxJava, je vous recommande d'écrire votre classe MediatorLiveData personnalisée. Pour savoir comment écrire une classe MediatorLiveData personnalisée, consultez cet exemple: https://Gist.github.com/AkshayChordiya/a79bfcc422fd27d52b15cdafc55eac6b

0
Akshay Chordiya