В чем разница между этими двумя методами класса LiveData? Официальный документ и учебник довольно расплывчаты. В методе map() первый параметр называется source, но в switchMap() он называется триггером. Какое обоснование этого?
В чем разница между методами map() и switchMap()?
Ответ 1
Согласно документации
, android.arch.core.util.Function) rel=noreferrer>Transformations.map()
Применяет функцию к значению, хранящемуся в объекте LiveData, и распространяет результат в нисходящем направлении.
, android.arch.core.util.Function>) rel=noreferrer>Transformations.switchMap()
Подобно карте, применяет функцию к значению, хранящемуся в объекте LiveData, и развертывает и отправляет результат в нисходящем направлении. Функция, переданная в switchMap(), должна возвращать объект LiveData.
Другими словами, я могу быть не на 100% прав, но если вы знакомы с RxJava; Transformations#map
похожа на Observable#map
& Transformations#switchMap
похожа на Observable#flatMap
.
Давайте возьмем пример, есть LiveData, который испускает строку, и мы хотим отобразить эту строку заглавными буквами.
Один подход будет следующим: в деятельности или фрагменте
Transformations.map(stringsLiveData, String::toUpperCase)
.observe(this, textView::setText);
функция, переданная на map
возвращает только строку, но это Transformation#map
которая в конечном итоге возвращает LiveData
.
Второй подход; в деятельности или фрагменте
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;
}
Если вы видите Transformations#switchMap
фактически переключил LiveData
. Итак, снова в соответствии с документацией Функция, передаваемая в switchMap(), должна возвращать объект LiveData.
Таким образом, в случае map
это исходные данные LiveData
вы преобразуете, а в случае switchMap
переданные данные LiveData
будут действовать как триггер, при котором они будут переключаться на другие LiveData
после LiveData
и отправки результата в нисходящем направлении.
Ответ 2
Мое замечание состоит в том, что если процесс преобразования выполняется быстро (не включает операцию базы данных или сетевую активность), вы можете использовать map
.
Однако, если ваш процесс преобразования медленный (с использованием операции с базой данных или сетевой активностью), вам нужно использовать switchMap
switchMap
используется при выполнении трудоемкой работы
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
не подходит для длительной работы
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);
}
}
Ответ 3
Прежде всего, методы map()
и switchMap()
вызываются в основном потоке. И они не имеют ничего общего с быстрыми или медленными задачами. Однако это может вызвать задержки в пользовательском интерфейсе, если вы выполняете сложные вычислительные или трудоемкие задачи внутри этих методов вместо рабочего потока, например, разбираете или преобразуете длинный и/или сложный ответ json, поскольку они выполняются в потоке пользовательского интерфейса.
- карта()
код метода map()
@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;
}
Он использует исходные данные LiveData, я - тип ввода и вызывает setValue (O) для LiveData, где O - тип вывода.
Для ясности приведу пример. Вы хотите записать имя пользователя и фамилию в textView при каждом изменении пользователя.
/**
* 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;
Теперь позвольте триггерным изменениям в mUserNameLiveData String при изменении mUserLiveData.
/*
* 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();
}
});
И давайте сделаем то же самое с 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());
}
});
И если вы наблюдаете MediatorLiveData для Activity или Fragment, вы получите тот же результат, что и для 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() возвращает одну и ту же MediatorLiveData, а не новую LiveData каждый раз, когда изменяется SourceLiveData.
Это исходный код
@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;
}
По сути, он создает конечную MediatorLiveData и устанавливает Result, как map ((), но эта функция времени возвращает 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;
}
Таким образом, map()
принимает LiveData<User>
и преобразует его в String
, если объект User
меняет, например, имя поля.
switchMap()
принимает String и получает LiveData<User>
используя его. Запросите пользователя из сети или базы данных со строкой и в результате получите LiveData<User>
.
Ответ 4
Map() концептуально идентичен использованию в RXJava, в основном вы меняете параметр LiveData на другой
Вместо SwitchMap() вы собираетесь заменить саму LiveData другой! Типичный случай, когда вы, например, извлекаете некоторые данные из репозитория и "удаляете" предыдущие LiveData (для сбора мусора, обычно для повышения эффективности использования памяти), вы передаете новые LiveData, которые выполняют то же действие (получая запрос для пример)