В чем разница между методами map() и switchMap()?

В чем разница между этими двумя методами класса LiveData? Официальный документ и учебник довольно расплывчаты. В методе map() первый параметр называется source, но в 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 на другой enter image description here

Вместо SwitchMap() вы собираетесь заменить саму LiveData другой! Типичный случай, когда вы, например, извлекаете некоторые данные из репозитория и "удаляете" предыдущие LiveData (для сбора мусора, обычно для повышения эффективности использования памяти), вы передаете новые LiveData, которые выполняют то же действие (получая запрос для пример)