web-dev-qa-db-ja.com

map()メソッドとswitchMap()メソッドの違いは何ですか?

LiveDataクラスのこれら2つのメソッドの違いは何ですか?公式のドキュメントとチュートリアルはそれについてかなりあいまいです。 map()メソッドでは、最初のパラメーターはsourceと呼ばれますが、switchMap()ではtriggerと呼ばれます。その背後にある理由は何ですか?

38
Igor Bubelov

ドキュメントに従って

Transformations.map()

LiveDataオブジェクトに保存されている値に関数を適用し、結果をダウンストリームに伝播します。

Transformations.switchMap()

Mapと同様に、LiveDataオブジェクトに保存されている値に関数を適用し、結果をアンラップして下流にディスパッチします。 switchMap()に渡される関数は、LiveDataオブジェクトを返す必要があります

言い換えれば、私は100%正確ではないかもしれませんが、RxJavaに精通しているなら。 Transformations#mapObservable#mapに似ています&Transformations#switchMapObservable#flatMapに似ています。

例を見てみましょう。文字列を出力するLiveDataがあり、その文字列を大文字で表示する必要があります。

1つのアプローチは次のとおりです。アクティビティまたはフラグメント内

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

mapに渡される関数は文字列のみを返しますが、最終的にLiveDataを返すのはTransformation#mapです。

2番目のアプローチ。アクティビティまたはフラグメント内

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の場合はsourceLiveDataであり、switchMap渡されたLiveDatatriggerとして機能し、アンラップおよびディスパッチ後に別のLiveDataに切り替えます。結果はダウンストリーム。

24
Rupesh

まず、map()メソッドとswitchMap()メソッドは両方ともメインスレッドで呼び出されます。また、高速タスクまたは低速タスクに使用されることとは無関係です。ただし、ワーカースレッドの代わりにこれらのメソッド内で複雑な計算または時間のかかるタスクを実行すると、UIで遅延が発生する可能性があります。たとえば、長いおよび/または複雑なjson応答は、UIスレッドで実行されるため、解析または変換されます。

  • map()

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を使用し、Iは入力タイプであり、Oが出力タイプであるLiveDataでsetValue(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;
_

次に、mUserLiveDataが変更されたときにmUserNameLiveDataの文字列で変更をトリガーしましょう。

_   /*
     * 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を監視すると、_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()は、SourceLiveDataが変更されるたびにnew LiveDataではなく同じMediatorLiveDataを返します。

ソースコードは

_@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を作成し、map does()のようにResultに設定されますが、今回の関数は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;
    }
_

したがって、Stringオブジェクトが名前フィールドの変更を変更した場合、map()は_LiveData<User>_を取り、それをUserに変換します。

switchMap()は文字列を受け取り、それを使用して_LiveData<User>_を取得します。 Stringを使用してwebまたはdbからユーザーを照会し、結果として_LiveData<User>_を取得します。

6
Thracian

私の観察では、変換プロセスが高速であれば(データベース操作やネットワークアクティビティを必要としない)、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);
    }
}
6
Cheok Yan Cheng

Map()はRXJavaでの使用と概念的に同じです。基本的に、別のLiveDataのパラメーターを変更しています enter image description here

SwitchMap()代わりに、LiveData自体を別のものに置き換えます!典型的なケースは、たとえばリポジトリからいくつかのデータを取得し、以前のLiveDataを「削除」して(ガベージコレクトし、メモリをより効率的にするため)、new同じアクションを実行するLiveData(たとえばクエリを取得する)

3
trocchietto