ticktakclockの日記

技術ポエムを綴ったりします。GitHub idも同じです (@ticktakclock)

LiveDataをどのレイヤー(Repository層)まで許容するか

こんにちは、tkyです。

先日、とあるcodelabをやっていて『Repository層でLiveDataって使っていいの?』と感じたことがあり。

この疑問からLiveDataを使用するレイヤーとそこから生じるメリット・デメリットなどを考察した内容をまとめておきます。

こういうことを考えながらプログラミングしてるんだなと感じていただけたらと思います。

結論から申し上げると『用法用量を守ればRepository層にLiveDataを使っても良い』になりました。

codelabs.developers.google.com

上記Codelabではkotlin coroutine Flowの使い方を学習するためにあえてそういった書き方をしているとも思いますが、後のYoutubeリンクのDevsummitでも同様の書き方が見られました。

LiveDataとは、といった事などに関することは触れません。

何を疑問視しているのか

私のLiveDataの普段の使い方はこんな感じです。特に言うことはないです。

  • ViewModel <-> Repository でデータの取得
  • 取得したデータをLiveDataにpostValue()する
  • FragmentまたはDataBindingでLiveDataをobserveしてViewに反映する
interface BookRepository {
  fun fetchBooks(): List<Books>
}

class BookViewModel(private val bookRepository: BookRepository): ViewModel() {
    private val _books = MutableLiveData<List<Book>>
    val books: LiveData<Book> = _books
    init {
       runCatching {
         val books = bookRepository.fetchBooks()
         _books.postValue(books)
       }
    }
}

class BookFragment: Fragment() {
    fun onViewCreated() {
        bookViewModel.books.observe(Observer{ books ->
           // リスト更新
        })        
    }    
}

このときのRepositoryでLiveDataを直接返すようにするのってありなの?という疑問です。

interface BookRepository {
  fun fetchBooks(): LiveData<List<Books>>
}
class BookViewModel(private val bookRepository: BookRepository) {
    val books: LiveData<Book> = bookRepository.fetchBooks()
}

LiveDataはViewModelまでかなと思っている派

疑問を持ったということは想定と実態が異なっていたためですが、以下の理由でRepository層にLiveDataを持ってくることにためらいを持っていたために生まれたものでした。

  • RepositoryのテストにAndroidライブラリの依存が入る
  • LiveDataはViewにリアルタイムに通知するものだからViewに近い層で利用するほうが管理しやすそう、という先入観

逆にこの2つくらいしかなくて、普通ViewModelまでしか使われていないから程度のことしか出てこないですし、大きい否定材料にもなりません。

Repository層までLiveDataを持ってきた場合何が良さそう?

  • ViewModelでデータを一次受けしないでよくなる
    • _books.postValue(books) のような記述をしなくて良いということ(特にメリットとも言えない・・・)
  • LiveDataが最新の値を保持するためキャッシュっぽい動きしてくれそう

逆にデメリットも考えてみました

  • LiveDataは複数でObserveするのには向いてなさそう
    • 2箇所でObserveしても1個しか処理しないということ
  • 特定の場合のみpostValueするみたいな仕様が入ってしまう場合Repositoryにロジックが入る。設計が乱れる。
    • RxならViewModel側でFilterすればRepositoryにロジックが入ることなく実装できそう
  • RepositoryのテストにAndroidライブラリの依存が入るためテストしづらい
    • 実際Contextは使われていないのでUnitテストで処理できますが例えばKotlin MultiPlatformProjectなどではLiveData使えない

RoomはLiveDataを返すクエリをサポートしている

冒頭にも書いたとおりFlowを導入するために敢えてRepository層までLiveDataを持ってきて書き換えるという手順をやっているのかなと思いましたが、

RoomでRepository層とDaoまでLiveData使ってるじゃん・・・!ということはダメというわけでもないのかと考えます。

developer.android.com

気になったのはこの一文

『このパターンは、データベースに格納されているデータと同期したデータが常に UI に表示されるようにするうえで役立ちます。』

こういったユースケースを実現する場合は許容しても良いのかもしれません。(前提としてRoomもAndroidライブラリだからLiveDataが入っててもおかしくない)

Dev Summit から得た知見

以下の'18セッションで実際にRepositoryにLiveDataを扱うケースが存在しており、許容されていそうということが伺えます。

youtu.be

また同時にアンチパターンについても解説がありました。

  • HTTPリクエストから得た巨大なデータ群にLiveDataを使うとその分がメモリにとどまる
    • APIのRepositoryには使わないほうが良さそう
  • LiveDataインスタンスを複数画面で共有すると意図しないUI更新が起こる可能性がある
  •  データに対してたくさんのオペレータ(filterなど)が必要ならRxを使いましょう
  • ライフサイクルやUIに関係ないところにLiveDataは使わない。そのために設計していない。(もしくはRxを使いましょう)
  • 1回きりのデータ(動画では 1ショットオペレーションと言っている)ならCoroutinesを使いましょう(もしくはRxを使いましょう)

以下の'19セッションでCoroutines FlowでLiveDataの扱い方を解説していました。

www.youtube.com

  • 去年('18)話したRepository層でLiveDataを使えることは話したが、LiveDataはRxのようにリアクティブストリームビルダーとして設計されていない。
  • LiveDataのかわりにCoroutines Flowでできるようになる

確かにFlowの場合Androidライブラリ関係ないのでMultiPlatformProject考慮しても特に問題なさそうだなと思いました。

まとめ

  • LiveDataはUIに紐づくことは忘れずに
  • データが1回きりの表示(1 shot operation)ならCoroutinesで良い、都度変わる(observable)ならLiveDataでも良い。
  • 永続化したデータの内容とUIの表示内容を常に同期したいときはRepositoryでLiveDataを返すのはありかも
  • 巨大なデータクラスをLiveDataで扱うとメモリ圧迫につながるので注意
  • 用法用量を守ればRepository層にLiveDataを使っても良いけどCoroutines Flowで同じことができる
    • ただしFlowの細かい部分はまだExperimentalなので完全に移行できるかと言われると微妙

何を実現したいのかによってどの技術を使うか正しく選択できるようになるのが今の自分には必要だなと思います。日々勉強ですね。

参考文献

developer.android.com

developer.android.com