Quelle est la différence entre ces 2 méthodes de la classe LiveData? Le document officiel et le tutoriel sont assez vagues à ce sujet. Dans la méthode map (), le premier paramètre appelé source mais dans la switchMap (), il a appelé trigger. Quelle est la raison derrière cela?
Selon la documentation
Applique une fonction à la valeur stockée dans l'objet LiveData et propage le résultat en aval.
Comme pour mapper, applique une fonction à la valeur stockée dans l'objet LiveData, décompresse et distribue le résultat en aval. La fonction transmise à switchMap () doit renvoyer un objet LiveData .
En d'autres termes, je ne suis peut-être pas tout à fait correct, mais si vous connaissez RxJava; Transformations#map
est un peu similaire à Observable#map
& Transformations#switchMap
est similaire à Observable#flatMap
.
Prenons un exemple, il existe un LiveData qui émet une chaîne et nous voulons l’afficher en majuscule.
Une approche serait la suivante: dans une activité ou un fragment
Transformations.map(stringsLiveData, String::toUpperCase)
.observe(this, textView::setText);
la fonction transmise à map
renvoie uniquement une chaîne, mais c'est le Transformation#map
qui retourne finalement un LiveData
.
La deuxième approche; dans une activité ou un fragment
Transformations.switchMap(stringsLiveData, this::getUpperCaseStringLiveData)
.observe(this, textView::setText);
private LiveData<String> getUpperCaseStringLiveData(String str) {
MutableLiveData<String> liveData = new MutableLiveData<>();
liveData.setValue(str.toUpperCase());
return liveData;
}
Si tu vois Transformations#switchMap
a en fait basculé le LiveData
. Donc, encore une fois selon la documentation La fonction passée à switchMap () doit retourner un objet LiveData .
Donc, dans le cas de map
c'est la source LiveData
que vous êtes en train de transformer et dans le cas de switchMap
_ le LiveData
passé agira comme un déclencheur sur lequel il basculera vers un autre LiveData
après le décompressage et l'envoi le résultat en aval.
Tout d'abord, les méthodes map()
et switchMap()
sont toutes deux invoquées sur le thread principal. Et ils n'ont rien à voir avec être utilisé pour des tâches rapides ou lentes. Toutefois, cela peut entraîner des décalages sur l'interface utilisateur si vous effectuez des tâches complexes de calcul au sein de ces méthodes au lieu d'un thread de travail, analysant ou convertissant une réponse json longue et/ou complexe par exemple, puisqu'elles sont exécutées sur le thread d'interface utilisateur.
le code de la méthode map () est
@MainThread
public static <X, Y> LiveData<Y> map(@NonNull LiveData<X> source,
@NonNull final Function<X, Y> func) {
final MediatorLiveData<Y> result = new MediatorLiveData<>();
result.addSource(source, new Observer<X>() {
@Override
public void onChanged(@Nullable X x) {
result.setValue(func.apply(x));
}
});
return result;
}
En fait, il utilise une source LiveData, I est le type d'entrée et appelle setValue (O) sur LiveData où O est le type de sortie.
Pour que ce soit clair, laissez-moi vous donner un exemple. Vous souhaitez écrire le nom d'utilisateur et le nom de famille dans textView chaque fois qu'un utilisateur change.
/**
* Changes on this user LiveData triggers function that sets mUserNameLiveData String value
*/
private MutableLiveData<User> mUserLiveData = new MutableLiveData<>();
/**
* This LiveData contains the data(String for this example) to be observed.
*/
public final LiveData<String> mUserNameLiveData;
déclenchons maintenant les modifications sur la chaîne de mUserNameLiveData lorsque mUserLiveData est modifié.
/*
* map() method emits a value in type of destination data(String in this example) when the source LiveData is changed. In this example
* when a new User value is set to LiveData it trigger this function that returns a String type
*
* Input, Output
* new Function<User, String>
*
* public String apply(User input) { return output;}
*/
// Result<Output> Source<Input> Input, Output
mUserNameLiveData = Transformations.map(mUserLiveData, new Function<User, String>() {
@Override
public String apply(User input) {
// Output
return input.getFirstName() + ", " + input.getLastName();
}
});
Et faisons la même chose avec MediatorLiveData
/**
* MediatorLiveData is what {@link Transformations#map(LiveData, Function)} does behind the scenes
*/
public MediatorLiveData<String> mediatorLiveData = new MediatorLiveData<>();
/*
* map() function is actually does this
*/
mediatorLiveData.addSource(mUserLiveData, new Observer<User>() {
@Override
public void onChanged(@Nullable User user) {
mediatorLiveData.setValue(user.getFirstName() + ", " + user.getLastName());
}
});
Et si vous observez MediatorLiveData à l'activité ou au fragment, vous obtenez le même résultat qu'en observant LiveData<String> mUserNameLiveData
userViewModel.mediatorLiveData.observe(this, new Observer<String>() {
@Override
public void onChanged(@Nullable String s) {
TextView textView = findViewById(R.id.textView2);
textView.setText("User: " + s);
Toast.makeText(MainActivity.this, "User: " + s, Toast.LENGTH_SHORT).show();
}
});
switchMap () retourne le même MediatorLiveData et non un nouvea LiveData à chaque fois que SourceLiveData est modifié.
Son code source est
@MainThread
public static <X, Y> LiveData<Y> switchMap(@NonNull LiveData<X> trigger,
@NonNull final Function<X, LiveData<Y>> func) {
final MediatorLiveData<Y> result = new MediatorLiveData<>();
result.addSource(trigger, new Observer<X>() {
LiveData<Y> mSource;
@Override
public void onChanged(@Nullable X x) {
LiveData<Y> newLiveData = func.apply(x);
if (mSource == newLiveData) {
return;
}
if (mSource != null) {
result.removeSource(mSource);
}
mSource = newLiveData;
if (mSource != null) {
result.addSource(mSource, new Observer<Y>() {
@Override
public void onChanged(@Nullable Y y) {
result.setValue(y);
}
});
}
}
});
return result;
}
Fondamentalement, il crée un MediatorLiveData final et est défini sur le résultat comme la carte le fait (), mais cette fonction renvoie LiveData.
public static <X, Y> LiveData<Y> map(@NonNull LiveData<X> source,
@NonNull final Function<X, **Y**> func) {
final MediatorLiveData<Y> result = new MediatorLiveData<>();
result.addSource(source, new Observer<X>() {
@Override
public void onChanged(@Nullable X x) {
result.setValue(func.apply(x));
}
});
return result;
}
@MainThread
public static <X, Y> LiveData<Y> switchMap(@NonNull LiveData<X> trigger,
@NonNull final Function<X, **LiveData<Y>**> func) {
final MediatorLiveData<Y> result = new MediatorLiveData<>();
result.addSource(trigger, new Observer<X>() {
LiveData<Y> mSource;
@Override
public void onChanged(@Nullable X x) {
LiveData<Y> newLiveData = func.apply(x);
if (mSource == newLiveData) {
return;
}
if (mSource != null) {
result.removeSource(mSource);
}
mSource = newLiveData;
if (mSource != null) {
result.addSource(mSource, new Observer<Y>() {
@Override
public void onChanged(@Nullable Y y) {
result.setValue(y);
}
});
}
}
});
return result;
}
Donc, map()
prend LiveData<User>
Et le transforme en String
, si User
objet change de nom de champ, par exemple.
switchMap()
prend une chaîne et obtient LiveData<User>
en l'utilisant. Interrogez un utilisateur sur le Web ou sur une base de données avec une chaîne et obtenez un résultat LiveData<User>
.
Mon observation est que, si votre processus de transformation est rapide (n'implique pas d'opération de base de données ni d'activité de mise en réseau), vous pouvez choisir d'utiliser map
.
Cependant, si votre processus de transformation est lent (opération impliquant une base de données ou activité réseau), vous devez utiliser switchMap
.
switchMap
est utilisé lors d'opérations fastidieusesclass MyViewModel extends ViewModel {
final MutableLiveData<String> mString = new MutableLiveData<>();
final LiveData<Integer> mCode;
public MyViewModel(String string) {
mCode = Transformations.switchMap(mString, input -> {
final MutableLiveData<Integer> result = new MutableLiveData<>();
new Thread(new Runnable() {
@Override
public void run() {
// Pretend we are busy
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int code = 0;
for (int i=0; i<input.length(); i++) {
code = code + (int)input.charAt(i);
}
result.postValue(code);
}
}).start();
return result;
});
if (string != null) {
mString.setValue(string);
}
}
public LiveData<Integer> getCode() {
return mCode;
}
public void search(String string) {
mString.setValue(string);
}
}
map
ne convient pas aux opérations fastidieusesclass MyViewModel extends ViewModel {
final MutableLiveData<String> mString = new MutableLiveData<>();
final LiveData<Integer> mCode;
public MyViewModel(String string) {
mCode = Transformations.map(mString, input -> {
/*
Note: You can't launch a Thread, or sleep right here.
If you do so, the APP will crash with ANR.
*/
/*
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
*/
int code = 0;
for (int i=0; i<input.length(); i++) {
code = code + (int)input.charAt(i);
}
return code;
});
if (string != null) {
mString.setValue(string);
}
}
public LiveData<Integer> getCode() {
return mCode;
}
public void search(String string) {
mString.setValue(string);
}
}
Map () est conceptuellement identique à l’utilisation dans RXJava. En gros, vous modifiez un paramètre de LiveData dans un autre
SwitchMap () Au lieu de cela, vous allez remplacer le LiveData lui-même par un autre! Le cas typique est lorsque vous récupérez des données d’un référentiel, par exemple, et que vous "éliminez" les LiveData précédents (afin de ramasser les ordures, pour rendre la mémoire plus efficace en général), vous transmettez un nouveau LiveData exécutant la même action (obtenir une requête par exemple)