LiveDataクラスのこれら2つのメソッドの違いは何ですか?公式のドキュメントとチュートリアルはそれについてかなりあいまいです。 map()メソッドでは、最初のパラメーターはsourceと呼ばれますが、switchMap()ではtriggerと呼ばれます。その背後にある理由は何ですか?
ドキュメントに従って
LiveDataオブジェクトに保存されている値に関数を適用し、結果をダウンストリームに伝播します。
Mapと同様に、LiveDataオブジェクトに保存されている値に関数を適用し、結果をアンラップして下流にディスパッチします。 switchMap()に渡される関数は、LiveDataオブジェクトを返す必要があります。
言い換えれば、私は100%正確ではないかもしれませんが、RxJavaに精通しているなら。 Transformations#map
はObservable#map
に似ています&Transformations#switchMap
はObservable#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
渡されたLiveData
はtriggerとして機能し、アンラップおよびディスパッチ後に別のLiveData
に切り替えます。結果はダウンストリーム。
まず、map()
メソッドとswitchMap()
メソッドは両方ともメインスレッドで呼び出されます。また、高速タスクまたは低速タスクに使用されることとは無関係です。ただし、ワーカースレッドの代わりにこれらのメソッド内で複雑な計算または時間のかかるタスクを実行すると、UIで遅延が発生する可能性があります。たとえば、長いおよび/または複雑なjson応答は、UIスレッドで実行されるため、解析または変換されます。
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()は、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>
_を取得します。
私の観察では、変換プロセスが高速であれば(データベース操作やネットワークアクティビティを必要としない)、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);
}
}