【MVVM】Transformations.switchMap() の使い方

👉 ViewModels and LiveData: Patterns + AntiPatterns – Android Developers – Medium 
👉 LiveData beyond the ViewModel — Reactive patterns using Transformations and MediatorLiveData 

LiveData をリアクティブに操作できるこのユーティリティメソッド。

LiveData を変換します。
これから返されるLiveData オブジェクトを Observe しておけば、Observer のライフサイクルを考慮しながら、データを変換することができます。lazy に処理され、呼び出しや依存関係の記述なしに、ライフサイクル関連の動作が引き継がれます。

👉 Transformations | android.arch.lifecycle.Transformations  |  Android Developers 

実体は MediatorLiveData のユーティリティ

ソースコードを見てみます。


@SuppressWarnings("WeakerAccess")
public class Transformations {

    private Transformations() {
    }

    @MainThread
    public static <X, Y> LiveData<Y> map(
            @NonNull LiveData<X> source,
            @NonNull final Function<X, Y> mapFunction) {
        final MediatorLiveData<Y> result = new MediatorLiveData<>();
        result.addSource(source, new Observer<X>() {
            @Override
            public void onChanged(@Nullable X x) {
                result.setValue(mapFunction.apply(x));
            }
        });
        return result;
    }

    @MainThread
    public static <X, Y> LiveData<Y> switchMap(
            @NonNull LiveData<X> source,
            @NonNull final Function<X, LiveData<Y>> switchMapFunction) {
        final MediatorLiveData<Y> result = new MediatorLiveData<>();
        result.addSource(source, new Observer<X>() {
            LiveData<Y> mSource;

            @Override
            public void onChanged(@Nullable X x) {
                LiveData<Y> newLiveData = switchMapFunction.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;
    }
}

👉 Cross Reference: Transformations.java 

ソースコードより、これは、1:1 の MediatorLiveData を使うための便利ツールです。


class MainViewModel {
  val repositoryResult = Transformations.switchMap(userManager.user) { user →
     repository.getDataForUser(user)
  }
}

複数に変換したい場合は、直接 MediatorLiveData を使うのが良いでしょう。


val liveData1: LiveData<Int> = ...
val liveData2: LiveData<Int> = ...

val result = MediatorLiveData<Int>()

result.addSource(liveData1) { value →
    result.setValue(value)
}
result.addSource(liveData2) { value →
    result.setValue(value)
}

まとめ

ViewModel 内で、その下にある Repository との間で使います。

View (Activity / Fragment) のライフサイクルにも考慮されています。

ViewModel内で、データの id からその詳細情報の取得をしたり、検索文字列をフィルターとしてデータ抽出したり、などに使うことが多いようです。

👉 MediatorLiveDataとTransformationsでViewModelを効果的に使う 


AbsentLiveData とは?

あちこちで見かける AbsentLiveData。

ViewModel の中で以下のように使われています。


val game: LiveData<RefreshableResource<GameEntity>> = Transformations.switchMap(_gameId) { gameId ->
    when (gameId) {
        BggContract.INVALID_ID -> AbsentLiveData.create()
        else -> gameRepository.getGame(gameId)
    }
}

調べてみると、

どうやら、null の入った LiveData のようです。


/**
 * A LiveData class that has `null` value.
 */
class AbsentLiveData<T : Any?> private constructor(): LiveData<T>() {
    init {
        // use post instead of set since this can be created on any thread
        postValue(null)
    }

    companion object {
        fun <T> create(): LiveData<T> {
            return AbsentLiveData()
        }
    }
}

👉 android-architecture-components/AbsentLiveData.kt at master · googlesamples/android-architecture-components 

うん、揃う。

これは、コピペで使っていきましょう。


Paging DataSourceFactory toLiveData() toObservable() が見つからない。

Javaのこのコードが、


public class ConcertViewModel extends ViewModel {
  private ConcertDao concertDao;
  public final Observable<PagedList<Concert>> concertList;

  public ConcertViewModel(ConcertDao concertDao) {
    this.concertDao = concertDao;
    concertList = new RxPagedListBuilder<>(
          concertDao.concertsByDate(), 50)
                  .buildObservable();
  }
}

Kotlinでこう書けるはずなのに!


class ConcertViewModel(concertDao: ConcertDao) : ViewModel() {
  val concertList: Observable<PagedList<Concert>> =
        concertDao.concertsByDate().toObservable(pageSize = 50)
}

Paging library overview  |  Android Developers

見つからない toObservable()。

- Added DataSourceFactory.toLiveData() as a Kotlin alternative for LivePagedListBuilder
- Added DataSourceFactory.toObservable() and toFlowable() as Kotlin alternatives for RxPagedListBuilder

Paging  |  Android Developers

どうやら -ktx のようです。


implementation "androidx.paging:paging-runtime-ktx:2.1.0"
implementation "androidx.paging:paging-rxjava2-ktx:2.1.0"

Maven Repository: androidx.paging

最近は、同じ処理でも複数の書き方があることが多くなって最初混乱したりします。