LiveDataコンポーネントの使い方編【Android Jetpack】

Android

Android JetpackのLiveDataってなに?

LiveDataはどうやって使ったらいいの?

Android JetpackのLiveDataを使ったことがない方のこんな疑問を解決します。

LiveDataの動きや使い方を理解しないままざっくり適用してしまうとパフォーマンス悪化につながります。

この記事を読むことで、LiveDataの使い方や注意点がわかるようになります。
簡単なサンプルアプリを用意していますので、実際に自分の手で動かして動きをつかんでみてください。

本記事ではサンプルアプリを作りながら大きく以下の3点を説明します。

  • LiveDataとは何?
  • LiveDataの正しい使い方と注意点
  • サンプルアプリを作りながらLiveDataの扱い方

LiveDataを使ったことがない方や、プロジェクトでLiveDataを使っているけど少し不安といった方は、是非記事をご覧ください。

スポンサーリンク

LiveDataとは?

LiveDataはライフサイクルに応じた監視が可能なデータホルダークラスです。

LiveDataの監視を開始するときに観察者のライフサイクルを渡すことで、そのライフサイクルに応じて通知を行います。

特徴

観察者のライフサイクルの状態が STARTED または RESUMED の場合、LiveDataはその観察者がアクテイブな状態であると判断します。
そして、LiveDataは更新情報をアクティブな観察者のみに通知を送ります。

また、LiveDataに渡された観察者のライフサイクルの状態が DESTROYED に変わったときに、LiveDataのが自動で観察者の監視を解除します。

LiveDataオブジェクトを安全に監視でき、リークについて心配することが減るため非常に有用です。
ActivityやFragmentで使用する場合、これらが破棄されるときに自動で監視を解除してくれるため、ActivityやFragmentから監視解除のメッセージを送る必要がなくなり、リークのリスクが減ることになります。
※ただし注意は必要で、このあたりの挙動についてはしっかり理解しておく必要があります。

メリット

自分が感じているメリットを3つ上げます。

LiveDataのメリット

  1. 変更通知に応じて処理をするだけ
  2. ライフサイクルに応じてよしなに通知する
  3. DataBindingと合わせて更に活躍

1. 変更通知に応じて処理をするだけ

とても実装がシンプルで、考慮が楽になります。

例えば、
『ある処理の中でデータの更新したから、その後Viewを更新する処理を呼んであげて…。』
『こっちの処理の中でもデータ更新したから、その後Viewを更新する処理を呼んであげて…。』
みたいなことをやらなくて良いのです。

LiveDataを監視しておき、変更通知を受け取ったらそのデータでViewを更新だけで良くなります。

2. ライフサイクルに応じてよしなに通知する

特徴の項目で説明しましたが、アクテイブな観察者に通知をしてくれるんです。

例えば、
あるActivityのクラス’A’がLiveDataを監視して通知を受け取るようにしたとします。
‘A’が画面を表示している間は通知を受け取りたいですが、別のActivityに遷移して’A’が表示されていない間は通知を受け取る必要ないですよね?

それをよしなにLiveDataで管理をしてくれます。
そして、’A’の画面に戻ったときには最新のデータで通知されます。

3. DataBindingと合わせて更に活躍

DataBindingと組み合わせると、xml上でLiveDataを使うように定義するだけで、LiveDataの値が更新されると自動でUIが更新されるようにできます。

LiveDataを監視する処理自体、ActivityやFragmentに作る必要がなくなる場合もあります。

DataBindingについてはまた別途記事で説明する予定です。

デメリット

デメリットを上げるとすると以下です。

LiveDataのデメリット

  1. 注意して扱わないとクラッシュはもちろん、メモリ使用量も増えてしまう

1. 注意して扱わないとクラッシュはもちろん、メモリ使用量も増えてしまう<

注意しないとクラッシュやメモリ使用量が増えてしまうなんて、何にでも言えることですがね。

こちらについては次の『LiveDataを利用する上での注意点』で説明します。

LiveDataを利用する上での注意点

注意点

  1. Fragmentで監視するとき、LifecycleOwnerの引数にviewLifecycleOwnerを渡すこと
  2. MutableLiveDataへのデータセット時はスレッドに注意すること
  3. observeForeverでLiveDataを監視するときにはremoveObserverをすること
  4. 外部への公開はMutableLiveDataではなくLiveDataにすること

Fragmentで監視するとき、LifecycleOwnerの引数にviewLifecycleOwnerを渡すこと

FragmentでLiveDataを監視を開始するタイミングは、onCreateViewからあと(onViewCreatedや onActivityCreatedなど)で行われることが多いと思います。
ここで、LifecycleOwnerの引数にthisを渡してしまうとメモリリークにつながります。

Fragmentのライフサイクルの状態が DESTROYED に変わるまでに、onCreateViewより後は複数回コールされる可能性があるため、前の監視が開放されずに残ってしまい、そのまま新しく監視が開始してしまう場合があります。
例えば、Fragmentから別のFragmentに画面遷移してから再度戻ってきた時が該当します。

viewLifecycleOwnerはonDestroyViewの少し前のタイミングで DESTROYED になるため、viewLifecycleOwnerを渡すことでこれを解決できます。

MutableLiveDataへのデータセット時はスレッドに注意すること

MutableLiveDataへのデータセット方法は2種類あります。

  • setValue
  • postValue

setValue はメインスレッド以外で実行されるとExceptionがthrowされます。
postValue に関しては大丈夫なので、もしワーカースレッドで行われる可能性がある場合はpostValue を使うように注意しましょう。

observeForeverでLiveDataを監視するときにはremoveObserverをすること

メソッドの名前からわかるように監視したままとなります。
そのため、もし使用する場合はremoveObserverをどこで行うべきか検討が必要です。

LifecycleOwnerを渡す場合は、LiveDataが自動でクリーンアップしてくれるためはremoveObserverは必要ありません。

外部への公開はMutableLiveDataではなくLiveDataにすること

MutableLiveDataを外部に公開すると、外部から自由にデータの変更が可能になります。

カプセル化の観点からも外部から変更不要なものについては公開しないようにしましょう。

開発環境

サンプルアプリ作成時の開発環境は以下です。

  • macOS Catalina 10.15.2
  • Android Studio 3.5.3
  • Kotlin 1.3.61

また、今回作成したサンプルアプリのソースコードは githubのLiveDataSample に置いてあります。
ぜひ動かしてみてください。

作成するLiveDataサンプルアプリ

今回作成するサンプルアプリでは、ViewModelで管理しているcountデータをFragmentで表示し、「COUNT UP」ボタンをクリックすると、数値がインクリメントされるというミニマムなアプリです。

以下のように、countの変更通知を受け取り、そのまま表示するためのviewと、
countが一度変換かけられた値の変更通知を受け取り、表示するためのviewの2種類を用意します。
※レイアウトが少しおかしいですが気にしない。

また、作成するサンプルアプリではViewModelを使っていますが、ViewModelの使い方についてよく知らない方はこちらで紹介していますので一度読んでみてください。

ViewModelコンポーネントの使い方編【Android Jetpack】
Android JetpackのViewModelの使い方についてサンプルアプリを作りながら説明していきたいと思います。 ViewModelとは ViewModelとは、Android Jetpackに含まれるコンポーネントの1つで、画面を...

gradleに依存関係を追加

プロジェクトにLiveDataを追加していきます。

プロジェクト直下の build.gradle にLifecycleのバージョン設定を追加します。
※2020/01/26 時点の最新バージョンの 2.1.0 を追加します。

buildscript {
    ext {
        lifecycle_version = '2.1.0'
    }
}

app直下の build.gradle にViewModelの依存関係を追加します。

dependencies {
    implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
}

ViewModelの記事を見ている方はおわかりかと思いますが、ViewModelを使うために追加したコードでLiveDataも使えるようになっています。

ViewModelでLiveDataを扱う

SampleViewModelというViewModelを作り、そのクラスでLiveDataを使うようにしていきます。

class SampleViewModel : ViewModel() {

    private val mCount = MutableLiveData(0)

    val count: LiveData = mCount
    val countMessage: LiveData = Transformations.map(count) { count ->
            "count number is $count"
    }

    fun onClickCountUp() {  // COUNT UP ボタンを押したときのイベント
        val currentCount = mCount.value ?: throw Exception("Invalid _count value")
        mCount.value = currentCount + 1
    }

}

ポイントを説明します。

注意点で説明したように、外部に公開するプロパティはLiveDataの型にしています。

しかし、LiveDataだけだとデータの編集ができません。(setができない)
そのため、privateでMutableLiveDataのカウント数を管理し、外部に公開するものはLiveDataにしています。

また、Transformationsを使いcountの変更通知を受け、一度変換をかけて通知をするようにしています。
このようなテクニックも覚えておくと良いでしょう。

例えば、
ユーザIDを入力(または選択)して、そのIDをもとにユーザ情報を取得して画面にユーザ詳細を表示する動きを実現したいときに使えたりします。

以下がそんな場合のViewModelの例です。
※switchMapでLiveDataを変換しています。

class SelectUserViewModel : ViewModel() {

   private repository = UserRepository()
   private val selectedUserId = MutableLiveData<String>()

   val user: LiveData<User> = Transformations.switchMap(selectedUserId) { userId ->
            repository.getUser(userId)  // userIdを元にUser情報に変換
    }

    fun onSelectedUserId(userId: String) {
            selectedUserId.value = userId
    }

}

FragmentでLiveDataを監視

FragmentでViewModelのLiveDataを監視する処理を作ります。

class FirstFragment : Fragment(R.layout.fragment_first) {

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        val viewModel = ViewModelProviders.of(this)[SampleViewModel::class.java]

        view.findViewById<AppCompatButton>(R.id.countUpButton).apply {
            setOnClickListener {
                viewModel.onClickCountUp()
            }
        }

        val viewModelValueView = view.findViewById<AppCompatTextView>(R.id.viewModelValueView)
viewModel.count.observe(viewLifecycleOwner, Observer { count ->
            viewModelValueView.text = count.toString()
        })

        val countMessageView = view.findViewById<AppCompatTextView>(R.id.countMessageView)
            viewModel.countMessage.observe(viewLifecycleOwner, Observer { countMessage ->
            countMessageView.text = countMessage
        })
    }

}

注意点で説明したように、監視するときに渡すLifecycleOwnerはviewLifecycleOwnerを渡します。

あとは、変更された値が通知されるので、その値を用いてViewを更新するだけです。
簡単ですね。

LiveDataはviewLifecycleOwnerのライフサイクルに応じてよしなに通知してくれますし、不要になったらクリーンアップもしてくれるため監視の解除処理も不要です。

完成版デモ

「COUNT UP」ボタンを押すと、画面上部のViewが次々に更新されていくのがわかると思います。

簡単にViewの更新もできますし、ViewとViewModelで役割分担した実装ができています。

Viewはデータを表示するだけ、ViewModelはViewで表示するデータを管理する。

さいごに

LiveDataコンポーネントはいかがでしたか。
簡単ですがミニマムなアプリで使い方を説明してきました。

重要なことなのでもう一度言いますが、注意点で説明したことは気をつけてください
忘れてしまった方はもう一度確認しておくと良いでしょうか。

この記事であなたの疑問が解決できたら幸いです。