What is the difference between map () and switchMap () methods?

What is the difference between these two methods of the LiveData class? The white paper and the textbook are pretty vague. In the map () method, the first parameter is called source , but in switchMap () it is called a trigger . What is the rationale for this?

+28
source share
4 answers

According to the documentation

, android.arch.core.util.Function) rel = noreferrer> Transformations.map ()

Applies the function to a value stored in a LiveData object and propagates the result in a downstream direction.

, android.arch.core.util.Function>) rel = noreferrer> Transformations.switchMap ()

Like a map, applies a function to a value stored in a LiveData object, and expands and sends the result in a downstream direction. The function passed to switchMap () should return a LiveData object .

In other words, I may not be 100% right, but if you are familiar with RxJava; Transformations#map is similar to Observable#map & Transformations#switchMap is similar to Observable#flatMap .

Let's take an example, there is a LiveData that emits a string, and we want to display this string in capital letters.

One approach would be: in activity or fragment

 Transformations.map(stringsLiveData, String::toUpperCase) .observe(this, textView::setText); 

the function passed to map returns only a string, but it is Transformation#map which ultimately returns LiveData .

Second approach; in activity or 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; } 

If you see Transformations#switchMap actually switched LiveData . So again, according to the documentation, the Function passed to switchMap () should return a LiveData object .

Thus, in the case of map you convert the original LiveData data , and in the case of switchMap transmitted switchMap data will act as a trigger, in which they will switch to other LiveData after LiveData and send the result in a downstream direction.

+14
source

My observation is that if the conversion process is fast (does not include database operation or network activity), you can use map .

However, if your conversion process is slow (using a database operation or network activity), you need to use switchMap

switchMap used for time-consuming work

 class 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 not suitable for long work

 class 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); } } 
+6
source

First of all, the map() and switchMap() methods are called in the main thread. And they have nothing to do with fast or slow tasks. However, this can cause delays in the user interface if you perform complex computational or time-consuming tasks inside these methods instead of a workflow, for example, parse or transform a long and / or complex json response as they are executed in the user interface thread.

  • map()

map () method code

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

It uses the LiveData source data, I am the input type and calls setValue (O) for LiveData, where O is the output type.

For clarity, I will give an example. You want to write the username and surname in the textView each time the user changes.

  /** * 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; 

Now let the trigger changes to the mUserNameLiveData String when the mUserLiveData changes.

  /* * 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(); } }); 

And let's do the same with 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()); } }); 

And if you observe MediatorLiveData for Activity or Fragment, you will get the same result as for 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 ()

switchMap () returns the same MediatorLiveData, not the new LiveData every time SourceLiveData changes.

This is the source code.

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

Essentially, it creates the final MediatorLiveData and sets the Result as map ((), but this time function returns 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; } 

Thus, map() takes a LiveData<User> and converts it to String if the User object changes, for example, the name of the field.

switchMap() takes a String and gets a LiveData<User> using it. Request a user from the network or database with the string and as a result get LiveData<User> .

+4
source

Map () is conceptually identical to use in RXJava, basically you change the LiveData parameter to another enter image description here

Instead of SwitchMap (), you are going to replace LiveData itself with another! A typical case, for example, when you extract some data from the repository and β€œdelete” previous LiveData (to collect garbage, usually to increase memory efficiency), you transfer new LiveData that perform the same action (receiving a request for example)

+2
source

Source: https://habr.com/ru/post/1273764/


All Articles