Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Lifecycle, ViewModel, LiveData の復習

Lifecycle, ViewModel, LiveData の復習

Android Architecture Components 勉強会 #5
https://gdg-tokyo.connpass.com/event/123068/

Avatar for Yuki Anzai

Yuki Anzai

March 25, 2019
Tweet

More Decks by Yuki Anzai

Other Decks in Technology

Transcript

  1. Architecture Components 勉強会 とは • Architecture Components の基礎知識について学ぶ勉強会 • 主催:GDG

    Tokyo ハッシュタグ: #gdgtokyo • 講師、チューター:Google 社員と Google Developer Expert • (あんざいゆき、 えがわ、わさびーふ、あらき) • 当⽇チューター : @itome, @takahirom, @mokelab, @zaki50
  2. Architecture Components 勉強会 とは • 計4回の予定 • 第5回: Lifecycle, ViewModel,

    LiveData の復習 • 第6回: Room の復習, Paging • 第7回: WorkManager • 第8回: Navigation タイムテーブル 19:30~19:35 挨拶&説明 19:35~20:15 説明 20:15~20:25 休憩 20:25~ 課題取り組み
  3. Architecture Components とは 複数の機能・ライブラリの総称 • Data Binding • Lifecycles •

    LiveData • ViewModel • Room • Paging • WorkManager • Navigation
  4. Architecture Components とは 複数の機能・ライブラリの総称 • Data Binding • Lifecycles •

    LiveData • ViewModel • Room • Paging • WorkManager • Navigation 個別利⽤OK 組み合わせ利⽤OK
  5. Architecture Components とは 複数の機能・ライブラリの総称 • Data Binding • Lifecycles •

    LiveData • ViewModel • Room • Paging • WorkManager • Navigation 個別利⽤OK 組み合わせ利⽤OK #1 #2 #3 #4
  6. Architecture Components とは 複数の機能・ライブラリの総称 • Data Binding • Lifecycles •

    LiveData • ViewModel • Room • Paging • WorkManager • Navigation 個別利⽤OK 組み合わせ利⽤OK #1 #2 #3 #4 今⽇は これの 復習
  7. Architecture Components とは 複数の機能・ライブラリの総称 • Data Binding • Lifecycles •

    LiveData • ViewModel • Room • Paging • WorkManager • Navigation 個別利⽤OK 組み合わせ利⽤OK #1 #2 #3 #4 今⽇は これの 復習 #6 #7 #8
  8. Lifecycles • Lifecycles = Lifecycle-aware Components • ライフサイクルを感知するコンポーネント • Activity

    や Fragment のライフサイクル状態が変わったときに何 かアクションを起こす、ということができるようになる
  9. Lifecycle.State • 現在のライフサイクル状態を表す enum • INITIALIZED • DESTROYED • CREATED

    • STARTED • RESUMED https://developer.android.com/topic/libraries/architecture/lifecycle.html
  10. LifecycleOwner • クラスが Lifecycle を持つことを表す single method interface • FragmentActivity

    の親クラスである ComponentActivity が LifecycleOwner を実装している • Fragment は LifecycleOwner を実装している public interface LifecycleOwner { @NonNull Lifecycle getLifecycle(); }
  11. Lifecycle を取得する class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState:

    Bundle?) { super.onCreate(savedInstanceState) val lifecycle = lifecycle } } class MainFragment : Fragment() { override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) val lifecycle = lifecycle } }
  12. OnLifecycleEvent • 第1引数で LifecycleOwner を受け取ることができる • ON_ANY のみ第2引数で Lifecycle.Event を受け取ることができる

    val observer: LifecycleObserver = object : LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_START) fun onStart(source: LifecycleOwner) { } @OnLifecycleEvent(Lifecycle.Event.ON_ANY) fun onAny(source: LifecycleOwner, event: Lifecycle.Event) { } }
  13. LifecycleService • LifecycleOwner が実装された Service class MyService : LifecycleService() {

    override fun onCreate() { super.onCreate() val lifecycle = lifecycle } }
  14. ProcessLifecycleOwner • ON_START, ON_RESUME は、最初の Activity がそのイベントを通るとき に発⾏される • ON_STOP,

    ON_PAUSE は、最後の Activity がそのイベントを通るときに遅 延を伴って発⾏される • コンフィギュレーションチェンジによる再⽣成時は発⾏されない * 遅延は destruction 時のみ
  15. 主な LiveData • LiveData • 値の変更を Observe できるデータホルダー • MutableLiveData

    • 外部から変更可能な LiveData • MediatorLiveData • 複数の LiveData を束ねて管理する MutableLiveData
  16. 値の変更を Observe する • LifecycleOwner の Lifecycle が • アクティブのときに値が変更された

    → すぐに通知される • アクティブじゃないときに値が変更された → 次にアクティブになった ときに通知される @MainThread public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer)
  17. active / inactive • アクティブな Observer の数が1以上になったときに呼ばれる protected void onActive()

    protected void onInactive() • アクティブな Observer の数が1未満になったときに呼ばれる
  18. MutableLiveData • LiveData では値を変更するメソッドは protected • MutableLiveData では値を変更するメソッドが public になっている

    public void setValue(T value) public void postValue(T value) Main スレッド外から値を設定する際の便利メソッド 値を設定する(Main スレッドで呼び出すこと)
  19. MediatorLiveData • 複数の LiveData を束ねて管理できる • 型の異なる複数の LiveData にイベントを伝播させることができる @MainThread

    public <S> void addSource(@NonNull LiveData<S> source, @NonNull Observer<? super S> onChanged) @MainThread public <S> void removeSource(@NonNull LiveData<S> toRemote)
  20. Transformations • MediatorLiveData を使いやすくしたユーティリティ @MainThread public static <X, Y> LiveData<Y>

    map( @NonNull LiveData<X> source, @NonNull final Function<X, Y> mapFunction) @MainThread public static <X, Y> LiveData<Y> switchMap( @NonNull LiveData<X> source, @NonNull final Function<X, LiveData<Y>> switchMapFunction)
  21. ViewModel • Activity の画⾯回転時のデータ保持 • Activity の複数 Fragment 間でのデータ受け渡し •

    LiveData と併⽤することが多い • プロセス停⽌後は復旧できない • データの永続化ではない
  22. ViewModel の定義 • ViewModel または AndroidViewModel を継承する class MyViewModel :

    ViewModel() { } class MyViewModel(private val app: Application) : AndroidViewModel(app) { }
  23. ViewModel のインスタンス化 val viewModel: T = ViewModelProviders .of(<fragmentActivity || fragment>,

    factory) .get(<T : ViewModel>::class.java) • ViewModelProviders.of() と ViewModelProvider.get() を使う • ⾃分で new しない! val viewModel: MainViewModel = ViewModelProviders .of(this) .get(MainViewModel::class.java)
  24. ViewModelProvider.Factory • ViewModelProviders.of() の第2引数に指定する public interface Factory { @NonNull <T

    extends ViewModel> T create(@NonNull Class<T> modelClass); } val factory: ViewModelProvider.Factory = ... val viewModel: MainViewModel = ViewModelProviders .of(this, factory) .get(MainViewModel::class.java)
  25. distinctUntilChanged • 元 の LiveData の値が変わるまで emit しない LiveData を返す

    Transformations.distinctUntilChanged(liveData) .observe(this, Observer { ... }) https://developer.android.com/reference/androidx/lifecycle/Transformations.html#distinctUntilChanged(androidx.lifecycle.LiveData%3CX%3E)
  26. ViewModel.viewModelScope • lifecycle-viewmodel-ktx • ViewModel に紐づいた CoroutineScope • Dispatchers.Main val

    ViewModel.viewModelScope: CoroutineScope get() { val scope: CoroutineScope? = this.getTag(JOB_KEY) if (scope != null) { return scope } return setTagIfAbsent(JOB_KEY, CloseableCoroutineScope(Job() + Dispatchers.Main)) } implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0-alpha03"
  27. ViewModel Savedstate https://developer.android.com/reference/androidx/lifecycle/SavedStateVMFactory val savedMainViewModel = ViewModelProviders.of(this, SavedStateVMFactory(this)) .get(SavedMainViewModel::class.java) button.setOnClickListener

    { savedMainViewModel.update() } savedMainViewModel.liveData.observe(this, Observer { println(it) }) class SavedMainViewModel(private val handle: SavedStateHandle) : ViewModel() { private val _liveData = handle.getLiveData<Long>("time") val liveData: LiveData<Long> get() = _liveData fun update() { _liveData.value = System.currentTimeMillis() } }
  28. 課題 0.2 apply plugin: 'kotlin-kapt' ... dependencies { ... implementation

    'androidx.appcompat:appcompat:1.1.0-alpha03' ... def lifecycle_version = "2.0.0" implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version" kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version" testImplementation "androidx.arch.core:core-testing:$lifecycle_version" } app/build.gradle
  29. 課題 1.1 • Activity の各ライフサイクルメソッドで Lifecycle の currentState を確認 する

    • onCreate(), onStart(), onResume(), onPause(), onStop(), onDestroy(), onRestart(), onSaveInstanceState() class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) ... println("onCreate : ${lifecycle.currentState.name}") } }
  30. 課題 2.1 • LifecycleObserver の実装を⽤意して Lifecycle に登録する val lifecycleObserver =

    object : LifecycleObserver { } lifecycle.addObserver(lifecycleObserver)
  31. 課題 2.2 • 各 Lifecycle.Event を @OnLifecycleEvent に指定したメソッドを定義し、 呼び出されたときにログに出⼒する val

    lifecycleObserver = object : LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_CREATE) fun onCreate(source: LifecycleOwner) { println("ON_CREATE : ${source.lifecycle.currentState.name}") } ... @OnLifecycleEvent(Lifecycle.Event.ON_ANY) fun onAny(source: LifecycleOwner, event: Lifecycle.Event) { println("ON_ANY : ${source.lifecycle.currentState.name}") } }
  32. 課題 2.3 • ON_STOP 時に observer を解除すると、ON_DESTROY 時のメソッドが呼 ばれなくなるのを確認する •

    val lifecycleObserver = object : LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_STOP) fun onStop(source: LifecycleOwner) { source.lifecycle.removeObserver(this) } @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) fun onDestroy(source: LifecycleOwner) { println("ON_DESTROY : ${source.lifecycle.currentState.name}") } }
  33. 課題 4.0 • Application を継承した MainApplication を⽤意する • MainApplication を

    AndroidManifest.xml に設定する class MainApplication : Application() { } <application android:name=".MainApplication" ...> ... </application>
  34. 課題 4.1 • MainApplication の onCreate() で LifecycleObserver の実装を⽤意し、 ProcessLifecycleOwner

    の Lifecycle に登録する class MainApplication : Application() { override fun onCreate() { super.onCreate() val lifecycleObserver = object : LifecycleObserver { } ProcessLifecycleOwner.get() .lifecycle .addObserver(lifecycleObserver) } }
  35. 課題 4.2 • LifecycleObserver に @OnLifecycleEvent に ON_ANY を指定したメソッ ド⽤意する

    val lifecycleObserver = object : LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_ANY) fun onAny(source: LifecycleOwner, event: Lifecycle.Event) { println("ON_ANY : ${source.lifecycle.currentState.name} $event") } }
  36. 課題 4.3 • いろんな操作をしたときのログを確認する • ホームキーをタップしたとき • Recent Apps から復帰したとき

    • 別の Activity に遷移したとき • 別の Activity から戻ってきたとき • 画⾯回転したとき
  37. • バックキーでアプリを終了したあと、ランチャーや RecentApps から起動 したとき ProcessLifecycleOwner で ON_CREATE は呼ばれる? •

    Recent Apps からクリアーしたあと、ランチャーから起動したとき ProcessLifecycleOwner で ON_CREATE は呼ばれる?
  38. 課題 5.1 • ボタンをタップしたとき、どのような順番で Observer に通知されるか確 認する val liveData =

    MutableLiveData<String>() liveData.observe(this, Observer { println(it) }) button.setOnClickListener { liveData.value = "a" liveData.value = "b" }
  39. 課題 5.2 • ボタンをタップしたとき、どのような順番で Observer に通知されるか確 認する val liveData =

    MutableLiveData<String>() liveData.observe(this, Observer { println(it) }) button.setOnClickListener { liveData.postValue("a") liveData.value = "b" }
  40. 課題 5.3 • ボタンをタップしたとき、どのような順番で Observer に通知されるか確 認する val liveData =

    MutableLiveData<String>() liveData.observe(this, Observer { println(it) }) button.setOnClickListener { liveData.postValue("a") liveData.postValue("b") }
  41. 課題6.1 • LiveData を継承した MainLiveData を⽤意し、onActive() と onInactive() で hasActiveObservers()

    の結果を確認する class MainLiveData : LiveData<String>() { override fun onActive() { super.onActive() println("onActive : ${hasActiveObservers()}") } override fun onInactive() { super.onInactive() println("onInactive : ${hasActiveObservers()}") } }
  42. 課題6.2 • MainActivity の onCreate() で MainLiveData を⽣成する • MainLiveData

    を observe し、いろんな操作をしたときのログを確認する • ホームキーをタップしたとき • Recent Apps から復帰したとき • 別の Activity に遷移したとき • 別の Activity から戻ってきたとき • 画⾯回転したとき
  43. 課題6.3 • ToggleButton を⽤意し、ボタンのチェックが切り替わったら MainLiveData を observe/removeObservers する • observe/removeObservers

    したときのログを確認する val liveData = MainLiveData() toggleButton.setOnCheckedChangeListener { _, isChecked -> if (isChecked) { liveData.observe(this, Observer { println(it) }) } else { liveData.removeObservers(this) } }
  44. 課題7.1 • CountUpLiveData を⽤意する class CountUpLiveData : LiveData<Int>() { private

    var count = 0 private val handler = Handler() private val r = Runnable { count++ value = count next() } private fun next() { handler.postDelayed(r, 1000) } override fun onActive() { next() } override fun onInactive() { handler.removeCallbacks(r) } }
  45. 課題7.2 実装例 class MainActivity : AppCompatActivity() { private val liveData

    = CountUpLiveData() private val observer = Observer<Int> { println("observeForever : $it") } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) liveData.observe(this, Observer { println(it) }) liveData.observeForever(observer) } override fun onDestroy() { super.onDestroy() liveData.removeObserver(observer) } }
  46. 課題8.1 実装例 class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState:

    Bundle?) { super.onCreate(savedInstanceState) Transformations .map(CountUpLiveData()) { it * it } .observe(this, Observer { println(it) }) } }
  47. 課題9.1 • CountUpLiveData を source とする MediatorLiveData を作って observe する

    • CountUpLiveData から来た値の2乗値を持つ • CountUpLiveData から来た値が10以上になったら source から外す • 実装例は次のスライド
  48. 課題9.1 実装例 MediatorLiveData<Int>() .apply { val source = CountUpLiveData() addSource(source)

    { if (it < 10) { value = it * it } else { removeSource(source) } } } .observe(this, Observer { println(it) })
  49. 課題10.1 • CountUpLiveData2 を⽤意する class CountUpLiveData2 : LiveData<Int>() { private

    var count = 0 private val handler = Handler() private val r = Runnable { count++ value = count next() } private fun next() { handler.postDelayed(r, 1000) } fun start() { next() } fun stop() { handler.removeCallbacks(r) } }
  50. 課題10.1 • 課題9.1 の source を CountUpLiveData2 に変える • CountUpLiveData2

    を observeForever() し、ログに出⼒する • アプリがバックグラウンドに⾏ったときのログを確認する • addSource で渡す Observer がどのようなときに呼ばれるのか確認する • 実装例は次のスライド
  51. class MainActivity : AppCompatActivity() { private val countUpLiveData2 = CountUpLiveData2()

    private val observer = Observer<Int> { println("observeForever : $it") } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) countUpLiveData2.observeForever(observer) MediatorLiveData<Int>() .apply { addSource(countUpLiveData2) { if (it < 10) { value = it * it } else { removeSource(countUpLiveData2) } } } .observe(this, Observer { println(it) }) countUpLiveData2.start() } override fun onDestroy() { super.onDestroy() countUpLiveData2.stop() countUpLiveData2.removeObserver(observer) } }
  52. 課題11.2 • この場合 viewModel1 と viewModel2 は同じインスタンスになるか確認する • 画⾯回転した場合、回転後と回転前で同じインスタンスになるか確認する class

    MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val viewModel1 = ViewModelProviders.of(this).get(MainViewModel::class.java) val viewModel2 = ViewModelProviders.of(this).get(MainViewModel::class.java) } }
  53. 課題12.1 • MainActivity に MainFragment を追加する class MainActivity : AppCompatActivity()

    { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) if (savedInstanceState == null) { supportFragmentManager.beginTransaction() .add(MainFragment(), "MainFragment") .commit() } val viewModel1 = ViewModelProviders.of(this).get(MainViewModel::class.java) } }
  54. 課題12.2 • この場合 viewModel1, viewModel2, viewModel3 は同じインスタンスになるか確認する • 画⾯回転した場合、回転後と回転前で同じインスタンスになるか確認する class

    MainFragment : Fragment() { override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) val viewModel2 = ViewModelProviders.of(this).get(MainViewModel::class.java) val viewModel3 = ViewModelProviders.of(requireActivity()) .get(MainViewModel::class.java) } }
  55. 課題13 class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?)

    { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val viewModel1 = ViewModelProviders.of(this).get("1", MainViewModel::class.java) val viewModel2 = ViewModelProviders.of(this).get("2", MainViewModel::class.java) } } • この場合 viewModel1, viewModel2 は同じインスタンスになるか確認する • 画⾯回転した場合、回転後と回転前で同じインスタンスになるか確認する
  56. 課題13.2 • MainActivity に MainFragment2 を 2つ 追加する class MainActivity

    : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) if (savedInstanceState == null) { supportFragmentManager.beginTransaction() .add(MainFragment2.newInstance(1), "MainFragment21") .add(MainFragment2.newInstance(2), "MainFragment22") .commit() } val viewModel1 = ViewModelProviders.of(this).get("1", MainViewModel::class.java) val viewModel2 = ViewModelProviders.of(this).get("2", MainViewModel::class.java) } }
  57. • この場合 viewModel1, viewModel2, viewModel3 の関係がどうなるか確認する • 画⾯回転した場合、回転後と回転前で同じインスタンスになるか確認する class MainFragment2

    : Fragment() { override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) val index = arguments!!.getInt("index") val viewModel3 = ViewModelProviders.of(requireActivity()) .get(index.toString(), MainViewModel::class.java) } companion object { fun newInstance(index: Int) = MainFragment2().apply { arguments = bundleOf("index" to index) } } }
  58. 課題14.1 • 課題7.1 の CountUpLiveData を ViewModel で保持し、Activity から observe

    する • 画⾯回転したときに Observer に渡される値がどうなるか確認する
  59. class MainViewModel : ViewModel() { val countUpLiveData = CountUpLiveData() }

    class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val viewModel: MainViewModel = ViewModelProviders .of(this) .get(MainViewModel::class.java) viewModel.countUpLiveData.observe(this, Observer { println(it) }) } }
  60. 課題15 class MainActivity : AppCompatActivity() { private val liveData =

    MutableLiveData<Int>().apply { value = 0 } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) button.setOnClickListener { val count = liveData.value = count } liveData.observe(this, Observer { println("count = $it") }) } } • ViewModel を導⼊して、画⾯回転しても count が 0 に戻らないようにする
  61. class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java) button.setOnClickListener { viewModel.countUp() } viewModel.liveData.observe(this, Observer { println("count = $it") }) } } class MainViewModel : ViewModel() { private val _liveData = MutableLiveData<Int>().apply { value = 0 } val liveData: LiveData<Int> get() = _liveData fun countUp() { val count = (_liveData.value ?: 0) + 1 _liveData.value = count } }
  62. 課題16 • 以下の仕様を満たすように次スライドの RandomLoadLiveData を完成させ る • onActive() が呼ばれたとき値が null

    または Status.Error の場合 load() を ⾏う • reload() が呼ばれたとき Load 中じゃなければ load() を⾏う • load() が呼ばれたら⼀秒後に Random.nextBoolean() を呼び、true の場 合は Status.Success(System.currentTimeMillis())を、false の場合 Status.Error を値にセットする
  63. sealed class Status<out T> { object Loading : Status<Nothing>() data

    class Success<out T>(val value: T) : Status<T>() data class Error(val throwable: Throwable) : Status<Nothing>() } class RandomLoadLiveData : LiveData<Status<Long>>() { // TODO } class MainViewModel : ViewModel() { // TODO }
  64. class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java) button.setOnClickListener { viewModel.reload() } viewModel.liveData.observe(this, Observer { when(it) { Status.Loading -> { progressBar.visibility = View.VISIBLE button.visibility = View.GONE } is Status.Success -> { progressBar.visibility = View.GONE button.visibility = View.VISIBLE button.text = it.value.toString() } is Status.Error -> { progressBar.visibility = View.GONE button.visibility = View.VISIBLE button.text = it.throwable.message } } }) } }
  65. class RandomLoadLiveData : LiveData<Status<Long>>() { private val handler = Handler()

    private val r = Runnable { value = if (Random.nextBoolean()) { Status.Success(System.currentTimeMillis()) } else { Status.Error(IllegalStateException("failed")) } } override fun onActive() { if (value == null || value is Status.Error) { load() } } fun reload() { if (value !is Status.Loading) { load() } } private fun load() { value = Status.Loading handler.postDelayed(r, 1000) } fun cancel() { handler.removeCallbacks(r) } } class MainViewModel : ViewModel() { private val _liveData = RandomLoadLiveData() val liveData: LiveData<Status<Long>> get() = _liveData fun reload() { _liveData.reload() } override fun onCleared() { super.onCleared() _liveData.cancel() } }