Level Up with LiveData (JP)

Level Up with LiveData (JP)

Android Dev Summit 2018 報告会

6dd0483f1353a4a359e92633cfd65c64?s=128

Daichi Furiya (Wasabeef)

December 10, 2018
Tweet

Transcript

  1. Level Up with LiveData Daichi Furiya (wasabeef) Android Dev Summit

    2018 報告会
  2. About me Daichi Furiya Google Developers Expert CATS @wasabeef_jp wasabeef

  3. まずは簡単におさらい LiveData + ViewModel

  4. Observable Lifecycle-aware Data holder Simple LiveData ( + ViewModel)

  5. Observable

  6. Observable Component1 Component2 class Component1( private val cmp2: Component2 )

    { "// ""... fun onDataLoaded(data: Data) { cmp2.setData(data) } } Component1 から Component2 へのデータを渡すシンプルな方法
  7. Observable ViewModel Activity class MyViewModel( private val activity: Activity )

    { "// ""... fun onDataLoaded(data: Data) { activity.setData(data) } } ViewModel が Activity を持つと何が悪いでしょうか?
  8. Observable Activity/ViewModel は それぞれ異なるライフサ イクルがあるということ を知っておいたほうがい いでしょう

  9. Observable ViewModel Activity Activity ViewModel では ViewModel が Activity の参照を持つのではなく

    Activity が ViewModel の参照を持つようにした場合は?
  10. LiveData は Observable の一種で Activity よりも寿命が長い

  11. LiveData は Observable の一種 class MyViewModel : ViewModel() { val

    myObservable: LiveData<String> = "//""... } class MyActivity : Activity() { val viewModel: MyViewModel = "//""... override fun onCreate(""...) { viewModel.myObservable.observe(this, Observer { data "-> title = data.title }) } } 第二引数はオブザーバで、値が変更されたことを知ることができる
  12. Lifecycle-aware

  13. Lifecycle-aware Lifecycle
 (Activity, Fragment) ライフサイクルを LiveData が知っている必要がある LiveData

  14. Lifecycle-aware class MyActivity : Activity() { val viewModel: MyViewModel =

    "//""... override fun onCreate(""...) { viewModel.myObservable.observe(this, Observer { data "-> title = data.title }) } } 第一引数でどのライフサイクル(LifecycleOwner)に合わせるか
  15. Lifecycle-aware LiveData#observe で 登録したオブザーバは STARTED or RESUMED でアクティブになり、 DESTROY 時には自動

    で解除してくれる
  16. Lifecycle-aware class MyActivity : Activity() { val viewModel: MyViewModel =

    "//""... override fun onCreate(""...) { viewModel.myObservable.observeForever { data "-> title = data.title } } } LiveData#observeForever ではアクティブかどうかは問わない テストで使うことが多い
  17. Data holder

  18. Data holder LiveData の値を設定 すると Activity がア クティブの場合は、そ の値が渡されます

  19. Data holder LiveData の値を設定 すると Activity がア クティブの場合は、そ の値が渡されます

  20. Data holder LiveData の値を設定 すると Activity がア クティブの場合は、そ の値が渡されます

  21. Data holder LiveData の値を設定 すると Activity がア クティブの場合は、そ の値が渡されます

  22. Data holder LiveData の値を設定 すると Activity がア クティブの場合は、そ の値が渡されます

  23. Data holder アクティブではない時 に LiveData の値が変 更されても Activity には渡されません

  24. Data holder アクティブではない時 に LiveData の値が変 更されても Activity には渡されません

  25. Data holder Activity がアクティブ になった時に既に LiveData に値が設定 されていた場合は、一 度 Activity

    に渡され ます
  26. Data holder その後は同じです

  27. Data holder その後は同じです

  28. Data holder その後は同じです

  29. Transformations

  30. map switchMap (MediatorLiveData) Transformations

  31. Transformations#map map で他の LiveData に変換することが可能

  32. Transformations#map "// Transformations.java public static <X, Y> LiveData<Y> map( LiveData<X>

    source, final Function<X, Y> mapFunction) { ""... } map で他の LiveData に変換することが可能
  33. Transformations#map val viewModelResult: LiveData<UIModel> = Transformations.map(repo.getData()) { data "-> parseDataToUIModel(data)

    } map で他の LiveData に変換することが可能
  34. Transformations#map enum class Type { DOG, CAT, BIRD } data

    class Animal(val type: Type, val name: String) class AnimalRepository { fun getAnimals() = "//""... } class DogViewModel(private val repo: AnimalRepository) : ViewModel() { private val animals = MutableLiveData<List<Animal">>() val dogs: LiveData<List<Animal">> = Transformations.map(animals) { list "-> list.filter { animal "-> animal.type "== Type.DOG } } fun getAnimals() { animals.postValue(repo.getAnimals()) } } 簡単なサンプル
  35. Transformations#map enum class Type { DOG, CAT, BIRD } data

    class Animal(val type: Type, val name: String) class AnimalRepository { fun getAnimals() = "//""... } class DogViewModel(private val repo: AnimalRepository) : ViewModel() { private val animals = MutableLiveData<List<Animal">>() val dogs: LiveData<List<Animal">> = Transformations.map(animals) { list "-> list.filter { animal "-> animal.type "== Type.DOG } } fun getAnimals() { animals.postValue(repo.getAnimals()) } } 例えば、データソースから取得できる Animal のデータがあるとします
  36. Transformations#map enum class Type { DOG, CAT, BIRD } data

    class Animal(val type: Type, val name: String) class AnimalRepository { fun getAnimals() = "//""... } class DogViewModel(private val repo: AnimalRepository) : ViewModel() { private val animals = MutableLiveData<List<Animal">>() val dogs: LiveData<List<Animal">> = Transformations.map(animals) { list "-> list.filter { animal "-> animal.type "== Type.DOG } } fun getAnimals() { animals.postValue(repo.getAnimals()) } } AnimalRepository を扱う DataViewModel に animals 変数を用意
  37. Transformations#map enum class Type { DOG, CAT, BIRD } data

    class Animal(val type: Type, val name: String) class AnimalRepository { fun getAnimals() = "//""... } class DogViewModel(private val repo: AnimalRepository) : ViewModel() { private val animals = MutableLiveData<List<Animal">>() val dogs: LiveData<List<Animal">> = Transformations.map(animals) { list "-> list.filter { animal "-> animal.type "== Type.DOG } } fun getAnimals() { animals.postValue(repo.getAnimals()) } } Repository を使ってデータを取得し、animals 変数を更新
  38. Transformations#map enum class Type { DOG, CAT, BIRD } data

    class Animal(val type: Type, val name: String) class AnimalRepository { fun getAnimals() = "//""... } class DogViewModel(private val repo: AnimalRepository) : ViewModel() { private val animals = MutableLiveData<List<Animal">>() val dogs: LiveData<List<Animal">> = Transformations.map(animals) { list "-> list.filter { animal "-> animal.type "== Type.DOG } } fun getAnimals() { animals.postValue(repo.getAnimals()) } } animals から dog だけのデータを管理したい
  39. Transformations#map enum class Type { DOG, CAT, BIRD } data

    class Animal(val type: Type, val name: String) class AnimalRepository { fun getAnimals() = "//""... } class DogViewModel(private val repo: AnimalRepository) : ViewModel() { private val animals = MutableLiveData<List<Animal">>() val dogs: LiveData<List<Animal">> = Transformations.map(animals) { list "-> list.filter { animal "-> animal.type "== Type.DOG } } fun getAnimals() { animals.postValue(repo.getAnimals()) } } Type によってフィルタリングし、DOG だけにする
  40. Transformations#map enum class Type { DOG, CAT, BIRD } data

    class Animal(val type: Type, val name: String) class AnimalRepository { fun getAnimals() = "//""... } class DogViewModel(private val repo: AnimalRepository) : ViewModel() { private val animals = MutableLiveData<List<Animal">>() val dogs: LiveData<List<Animal">> = Transformations.map(animals) { list "-> list.filter { animal "-> animal.type "== Type.DOG } } fun getAnimals() { animals.postValue(repo.getAnimals()) } } 簡単なサンプルでした
  41. Transformations#switchMap swichMap は値を別の LiveData に流してチェインすることができる

  42. Transformations#switchMap 例えば、UserManager が持つログインユーザデータが FirebaseAuth などを用いて更新されたとします

  43. Transformations#switchMap その FirebaseAuth から取得できる Twitter のトークンで サーバにユーザ情報を問い合わせる場合などに使うことができる

  44. Transformations#switchMap これは RxJava などでも出来るが ライフサイクルの管理は LiveData が行うので自分でする必要ない

  45. Transformations#switchMap Transformations#map で他の LiveData に変換することが可能 "// Transformations.java public static <X,

    Y> LiveData<Y> switchMap( LiveData<X> source, final Function<X, LiveData<Y">> switchMapFunction) { ""... }
  46. Transformations#switchMap class MyViewModel : ViewModel() { val repositoryResult = Transformations.switchMap(userManager.user)

    { user "-> repository.getDataForUser(user) } } 簡単に描くとこういう書き方になります
  47. アンチパターン

  48. 1. 大きいオブジェクトを Transformations で使う

  49. 1. 大きいオブジェクトを Transformations で使う Transformations で横断してメモリに展開される

  50. 2. LiveData を Activity 間で共有する

  51. class SharedLiveDataSource(val dataSource: MyDataSource) { fun loadDataForUser(userId: String): LiveData<Long> {

    val result = MutableLiveData<Long>() result.value = dataSource.getOnlineTime(userId) return result } } これだけ見ると特に問題なさそう... 2. LiveData を Activity 間で共有する
  52. 2. LiveData を Activity 間で共有する エッジケースではあるもの の、Lollipop で導入された Activity Transisions

    では 2つの Activity がアク ティブになることがある
  53. 3. Transformation を初期化時 以外でインスタンス化する

  54. class MyViewModel(private val repo: AnimalRepository) : ViewModel() { var animals

    = MutableLiveData<List<Animal">>() fun getAnimals() { animals = Transformations.switchMap(repo.getAnimals()) { it } } } オブザーバは新しいものが割り当てられたと判断できない 再登録が必要になるし、以前のものを解除していない 3. Transformation を初期化時以外でインスタンス化する
  55. class DogViewModel(private val repo: AnimalRepository) : ViewModel() { private val

    animals = MutableLiveData<List<Animal">>() val dogs: LiveData<List<Animal">> = Transformations.map(animals) { list "-> list.filter { animal "-> animal.type "== Type.DOG } } fun getAnimals() { animals.postValue(repo.getAnimals()) } } LiveData を var で定義しないことが大事 3. Transformation を初期化時以外でインスタンス化する
  56. 1. 大きいオブジェクトを Transformations で使う 2. LiveData を Activity 間で共有する 3.

    Transformation を初期化時以外でインスタンス化する アンチパターン
  57. 多くのオペレータやストリームが必要な場合は素直に RxJava を使いましょう UI に関するものやライフサイクルに関するものがない場合は 素直にコールバックを使うか、RxJava を使いましょう 複雑な処理(APIなど)を Promise のように繋げたいなら 

    Coroutines や RxJava を使いましょう + LiveData を使うべきではないパターン
  58. 多くのオペレータやストリームが必要な場合は素直に RxJava を使いましょう UI に関するものやライフサイクルに関するものがない場合は 素直にコールバックを使うか、RxJava を使いましょう 複雑な処理(APIなど)を Promise のように繋げたいなら 

    Coroutines や RxJava を使いましょう + LiveData を使うべきではないパターン
  59. 多くのオペレータやストリームが必要な場合は素直に RxJava を使いましょう UI に関するものやライフサイクルに関するものがない場合は 素直にコールバックを使うか、RxJava を使いましょう 複雑な処理(APIなど)を Promise のように繋げたいなら 

    Coroutines や RxJava を使いましょう + LiveData を使うべきではないパターン
  60. 多くのオペレータやストリームが必要な場合は素直に RxJava を使いましょう UI に関するものやライフサイクルに関するものがない場合は 素直にコールバックを使うか、RxJava を使いましょう 複雑な処理(APIなど)を Promise のように繋げたいなら 

    Coroutines や RxJava を使いましょう + LiveData を使うべきではないパターン
  61. 多くのオペレータやストリームが必要な場合は素直に RxJava を使いましょう UI に関するものやライフサイクルに関するものがない場合は 素直にコールバックを使うか、RxJava を使いましょう 複雑な処理(APIなど)を Promise のように繋げたいなら 

    Coroutines や RxJava を使いましょう + LiveData を使うべきではないパターン
  62. Resources Talk sessions: 
 - https://www.youtube.com/playlist?list=PLWz5rJ2EKKc8WFYCR9esqGGY0vOZm2l6e Web pages: 
 -

    https://medium.com/androiddevelopers/search?q=livedata Photos: - https://unsplash.com - https://www.pexels.com - http://www.iconarchive.com - https://www.irasutoya.com
  63. twitter.com/wasabeef_jp wasabeef.jp github.com/wasabeef