Slide 1

Slide 1 text

Level Up with LiveData Daichi Furiya (wasabeef) Android Dev Summit 2018 報告会

Slide 2

Slide 2 text

About me Daichi Furiya Google Developers Expert CATS @wasabeef_jp wasabeef

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

Observable

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

Observable ViewModel Activity class MyViewModel( private val activity: Activity ) { "// ""... fun onDataLoaded(data: Data) { activity.setData(data) } } ViewModel が Activity を持つと何が悪いでしょうか?

Slide 8

Slide 8 text

Observable Activity/ViewModel は それぞれ異なるライフサ イクルがあるということ を知っておいたほうがい いでしょう

Slide 9

Slide 9 text

Observable ViewModel Activity Activity ViewModel では ViewModel が Activity の参照を持つのではなく Activity が ViewModel の参照を持つようにした場合は?

Slide 10

Slide 10 text

LiveData は Observable の一種で Activity よりも寿命が長い

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

Lifecycle-aware

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

Lifecycle-aware class MyActivity : Activity() { val viewModel: MyViewModel = "//""... override fun onCreate(""...) { viewModel.myObservable.observe(this, Observer { data "-> title = data.title }) } } 第一引数でどのライフサイクル(LifecycleOwner)に合わせるか

Slide 15

Slide 15 text

Lifecycle-aware LiveData#observe で 登録したオブザーバは STARTED or RESUMED でアクティブになり、 DESTROY 時には自動 で解除してくれる

Slide 16

Slide 16 text

Lifecycle-aware class MyActivity : Activity() { val viewModel: MyViewModel = "//""... override fun onCreate(""...) { viewModel.myObservable.observeForever { data "-> title = data.title } } } LiveData#observeForever ではアクティブかどうかは問わない テストで使うことが多い

Slide 17

Slide 17 text

Data holder

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

Data holder その後は同じです

Slide 27

Slide 27 text

Data holder その後は同じです

Slide 28

Slide 28 text

Data holder その後は同じです

Slide 29

Slide 29 text

Transformations

Slide 30

Slide 30 text

map switchMap (MediatorLiveData) Transformations

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

Transformations#map "// Transformations.java public static LiveData map( LiveData source, final Function mapFunction) { ""... } map で他の LiveData に変換することが可能

Slide 33

Slide 33 text

Transformations#map val viewModelResult: LiveData = Transformations.map(repo.getData()) { data "-> parseDataToUIModel(data) } map で他の LiveData に変換することが可能

Slide 34

Slide 34 text

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>() val dogs: LiveData> = Transformations.map(animals) { list "-> list.filter { animal "-> animal.type "== Type.DOG } } fun getAnimals() { animals.postValue(repo.getAnimals()) } } 簡単なサンプル

Slide 35

Slide 35 text

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>() val dogs: LiveData> = Transformations.map(animals) { list "-> list.filter { animal "-> animal.type "== Type.DOG } } fun getAnimals() { animals.postValue(repo.getAnimals()) } } 例えば、データソースから取得できる Animal のデータがあるとします

Slide 36

Slide 36 text

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>() val dogs: LiveData> = Transformations.map(animals) { list "-> list.filter { animal "-> animal.type "== Type.DOG } } fun getAnimals() { animals.postValue(repo.getAnimals()) } } AnimalRepository を扱う DataViewModel に animals 変数を用意

Slide 37

Slide 37 text

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>() val dogs: LiveData> = Transformations.map(animals) { list "-> list.filter { animal "-> animal.type "== Type.DOG } } fun getAnimals() { animals.postValue(repo.getAnimals()) } } Repository を使ってデータを取得し、animals 変数を更新

Slide 38

Slide 38 text

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>() val dogs: LiveData> = Transformations.map(animals) { list "-> list.filter { animal "-> animal.type "== Type.DOG } } fun getAnimals() { animals.postValue(repo.getAnimals()) } } animals から dog だけのデータを管理したい

Slide 39

Slide 39 text

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>() val dogs: LiveData> = Transformations.map(animals) { list "-> list.filter { animal "-> animal.type "== Type.DOG } } fun getAnimals() { animals.postValue(repo.getAnimals()) } } Type によってフィルタリングし、DOG だけにする

Slide 40

Slide 40 text

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>() val dogs: LiveData> = Transformations.map(animals) { list "-> list.filter { animal "-> animal.type "== Type.DOG } } fun getAnimals() { animals.postValue(repo.getAnimals()) } } 簡単なサンプルでした

Slide 41

Slide 41 text

Transformations#switchMap swichMap は値を別の LiveData に流してチェインすることができる

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

Transformations#switchMap Transformations#map で他の LiveData に変換することが可能 "// Transformations.java public static LiveData switchMap( LiveData source, final Function> switchMapFunction) { ""... }

Slide 46

Slide 46 text

Transformations#switchMap class MyViewModel : ViewModel() { val repositoryResult = Transformations.switchMap(userManager.user) { user "-> repository.getDataForUser(user) } } 簡単に描くとこういう書き方になります

Slide 47

Slide 47 text

アンチパターン

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

2. LiveData を Activity 間で共有する

Slide 51

Slide 51 text

class SharedLiveDataSource(val dataSource: MyDataSource) { fun loadDataForUser(userId: String): LiveData { val result = MutableLiveData() result.value = dataSource.getOnlineTime(userId) return result } } これだけ見ると特に問題なさそう... 2. LiveData を Activity 間で共有する

Slide 52

Slide 52 text

2. LiveData を Activity 間で共有する エッジケースではあるもの の、Lollipop で導入された Activity Transisions では 2つの Activity がアク ティブになることがある

Slide 53

Slide 53 text

3. Transformation を初期化時 以外でインスタンス化する

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

class DogViewModel(private val repo: AnimalRepository) : ViewModel() { private val animals = MutableLiveData>() val dogs: LiveData> = Transformations.map(animals) { list "-> list.filter { animal "-> animal.type "== Type.DOG } } fun getAnimals() { animals.postValue(repo.getAnimals()) } } LiveData を var で定義しないことが大事 3. Transformation を初期化時以外でインスタンス化する

Slide 56

Slide 56 text

1. 大きいオブジェクトを Transformations で使う 2. LiveData を Activity 間で共有する 3. Transformation を初期化時以外でインスタンス化する アンチパターン

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

twitter.com/wasabeef_jp wasabeef.jp github.com/wasabeef