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

Lifecycle, ViewModel, LiveData の復習

Lifecycle, ViewModel, LiveData の復習

Android Architecture Components 勉強会

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() } }