(Android StudioとKotlinを使っています)

Roomを使ってSQLiteのデータを操作するよう書き換えていたところ、
ViewModelLiveDataも使った方が良さそうだという事がわかりました。
でも、どう連携するかわからないー…と思った時のメモです。

基本は公式から。

こちらの、公式サイトを見ながらやり方を確認してみました。
ViewModel Overview  |  Android Developers
わかりやすく書かれているのですが、英語なので後から確認しやすいように、必要そうなところだけ自分なりにまとめてみました。

ViewModelとは?

<消えちゃうデータを保存しとく!>

ActivityやFragmentは、画面を回転した時などに、UI関連のデータが消えちゃったりする事があります。
でも、ViewModelを使えば、これを消さずに保存し続ける事ができます。便利便利。

使い方としては、
例えば、Activity一つに対して、ViewModelを一つ用意して、
そのViewModelにデータを保存するようにすればいいようです。

<役割を分けるために使う!>

ActivityやFragmentに
全ての処理をずらーーっと書いたりする事もありますが、
そうすると、ActivityやFragmentに負荷がかかりがちになります。

そこで、
ActivityやFragmentでは画面周りの作業だけを行い、
他の処理は他でやる、という風にすると、スッキリしてきます。

そして、処理を分割することで、
分割した処理ごとのテストもしやすくなります。
(ActivityやFragment単位で全部テストしなくちゃいけない、って事が減ります)

そのためにも、
ViewModelにはデータの保持などの処理を任せた方が良いようです。

ViewModelの使い方メモ。 + LiveDataも使う

それでは、ViewModelを実際に使っていってみますー。

<MyViewModel.ktを準備する>

例えば、アプリでユーザーの一覧を表示する場合。
ユーザーの一覧を取得して・保持する事は、
ViewModelにやらせるようにしたいと思います。

ViewModelは、
例えば下記のような「MyViewModel.kt」というファイルを作って、
こんな風に書いていきます。
(ファイル名の「MyViewModel」の部分は、後に書くclass名に合わせて変えてください)

class MyViewModel : ViewModel() {

    //ここに、保存したい情報(ユーザーの一覧)を書く。
     private val users: MutableLiveData<List<User>> by lazy {
         MutableLiveData().also {
             loadUsers()
         }
     }

//保存した情報(ユーザー一覧)を取り出す
fun getUsers(): LiveData<List<User>> {
    return users
}

private fun loadUsers() {
    //ここで、ユーザーを取得するために、非同期操作を実行する
}

このサンプルの流れとしては、

1.ActivityなどからgetUsersという関数を呼ぶ。
2.getUsersは、usersを返してくれる。
  usersというのは、ViewModelに保存しておきたい「ユーザー一覧」のデータ。
  このusersを返すタイミングで、インスタンスを作り、非同期処理でユーザーのデータも取得してくる。
3.非同期処理の操作内容は、loadUsersという関数に書く。

という感じだと思います。

それでは、このサンプルを、上から少しずつ説明していきますー。

class MyViewModel : ViewModel() {

→この、MyViewModelというclass名は、場合により変えていきます。
 例えば、「UserActivity」という名前のActivityがあったとしたら、
 ViewModelは「UserViewModel」という名前にしたりするようです。
 (参考:android-architecture-components/UserViewModel.kt at master · googlesamples/android-architecture-components

private val users: MutableLiveDataListUser>> by lazy {

(コピペする時は、< は、 < に 置き換えてください)
→この場合のUserは、データベースのテーブル(@Entityで宣言したもの)型のデータです。
 例えば、useid(ユーザーのID)とusename(ユーザーの名前)のようなデータが入っています。
 ※ここで書いた「User」という名前は、扱うテーブルの名前によって、適宜変えてみてください。

 それを、何件かListに入れる形になります。
 そのListに入れたデータを、MutableLiveDataとして扱っていきます。
 (MutableLiveDataは、可変のLiveDataです)

 こんな感じで、
 Activityで使いたい「ユーザー一覧」のデータ(今回はusersという名前をつけています)を、
 ViewModelに保存しています。

 今回、このデータはprivate(他からは呼び出す事ができないデータ)になっています。
 そのため、このデータにアクセスする時は、アクセス用の関数を作り、そこからアクセスする形になります。

 by lazyは、最初に一度作れば、その後は同じインスタンスを使えるようになる、というもののようです(何度もインスタンスを作るという事がなくなるようです)。

MutableLiveData().also {
loadUsers()

→先ほどのusersは、呼び出す時にインスタンスも作るようにしてありました。
 そしてここではそのインスタンスを作るタイミングで、
 loadUsersという関数を呼び出して、
 その関数の中で非同期処理を行なうようにしているようです。
 (ここでいうloadUsersというのは、MyViewModel.ktの後ろの方に書かれている、
  private fun loadUsers() の事です)

fun getUsers(): LiveData<List<User>> {

(コピペする時は、< は、 < に 置き換えてください)
→先ほどのデータ(users)は、他からは呼び出す事ができない形にしてあるため、
 この「getUsers」という関数を使って呼び出して使います。

private fun loadUsers() {

→これは、MyViewModel.ktの最初の方にある、usersの近くに書かれていた、
 loadUsersの内容です。
 この中には、例えば、ユーザー一覧を取得するための非同期処理などを書きます。
 ※loadUsersという名前は、その時の処理内容によって、わかりやすい名前に変えてみてください。

<Activityなどからアクセスする>

ViewModelは準備できたので、早速使っていきます。
例えばこんな風に、Activityからアクセスするようです。

class MyActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        // システムがアクティビティのonCreate()メソッドを最初に呼び出すときにViewModelを作成します。
        // 再作成されたアクティビティは、最初のアクティビティによって作成されたものと同じMyViewModelインスタンスを受け取ります。

        val model = ViewModelProviders.of(this)[MyViewModel::class.java]
        model.getUsers().observe(this, Observer<List<User>>{ users ->
            //UIを更新する
        })
    }
}

上記のように、ViewModelは、例えばActivityのonCreateで作成します。

val model = ViewModelProviders.of(this)[MyViewModel::class.java]

→ここでViewModelを作成しています。
 ※「MyViewModel」には、先ほど準備したViewModelのclass名が入ります。

model.getUsers().observe(this, Observer<List<User>>{ users ->

(コピペする時は、< は、 < に 置き換えてください)
→ここで、MyViewModel.kt 内で準備したgetUsersを呼び出しています。
 返ってくるのはLiveDataなので、observeを使います。

ところで、ViewModelのライフサイクル。

Activity内で作成したViewModelは、Activityが一旦破棄されたり、再作成されたりしても
同じViewModelインスタンスが呼び出されるので、データを保持しつづけてくれます。
(そのため、画面が回転された時などにもデータが消えずに保持されます)

ただ、ViewModelはいつまでも消えない訳ではなく、
ViewModelに対するActivityが終了した時には
ViewModelオブジェクトのonCleared()を呼び出してリソースをクリーンアップをしてくれるようです。
(複数のActivityを作成した時にも、
 Activityが終了すれば一緒にViewModelもクリーンアップされるため、     
 ViewModelだけが消えずにメモリ上に大量に残って困る、という事もなさそうです)
 ここで言う「Activityが終了する」というのは、
 Finishedの状態になる(onDestroy後の状態になる)ということのようです。
 (先ほどのページの、The lifecycle of a ViewModelの図参照)

(Activityを使い続けている間は、それに対するViewModelは消えず、
 Activityを使い終わったら、それに対するViewModelも一緒に消えてくれるという
 イメージだと思います。)

そして、先ほど「画面が回転した時に、UI関連のデータが消えちゃうことがある」と書きましたが、
これは、
デバイスの画面が回転したときなどに、
ActivityのonCreateを何度か呼び出すことがあるという事らしいです。
このonCreateするタイミングで、UI関連のデータが消えちゃったりするみたいです。

でも、そんな時でも、ViewModelならUI関連のデータなども消さずに持っていてくれるという事のようです。

他に、フラグメント間のデータを共有する役目も。

Acticity内の2つ以上のFragmentがデータのやりとりなどをすることはよくありますが、
そんな時にもViewModelは便利に使えます。

例えば、
ユーザーがリストから項目を選択するFragmentと、
選択された項目の内容を表示する別のFragmentがあった場合。

データのやりとりが面倒だったり。
片方のFragmentは作られているのに、もう片方は作られていないため、データのやりとりが難しい時があったり。

そんな時も、ViewModelを使えば、やりとりがしやすくなるようです。

サンプルのコードは、以下のような感じですー
(こちらは特に、一行ずつ内容を調べてメモして行く事をしませんでしたが、ここまでの内容をふまえれば、なんとなく理解できるような気がしました)

class SharedViewModel : ViewModel() {
    val selected = MutableLiveData<Item>()

    fun select(item: Item) {
        selected.value = item
    }
}

class MasterFragment : Fragment() {

    private lateinit var itemSelector: Selector

    private lateinit var model: SharedViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        model = activity?.run {
            ViewModelProviders.of(this)[SharedViewModel::class.java]
        } ?: throw Exception("Invalid Activity")
        itemSelector.setOnClickListener { item ->
            // Update the UI
        }
    }
}

class DetailFragment : Fragment() {

    private lateinit var model: SharedViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        model = activity?.run {
            ViewModelProviders.of(this)[SharedViewModel::class.java]
        } ?: throw Exception("Invalid Activity")
        model.selected.observe(this, Observer<Item> { item ->
            // Update the UI
        })
    }
}

この方法をとると、

Activityには、Fragment同士のやりとりについて何も書かなくていい。
Fragment同士のやりとりは、ViewModelだけに書けばいい。
・片方のFragmentが消えても、もう片方のFragmentは通常通りに動く。

というようなメリットが出てくるようですー。

ViewModelを使うと、Loaderも便利に使える。

今までCursorLoaderのようなLoaderを使い、
UI内のデータを データベースと同期させていた場合。

そこを
ViewModel + Room + LiveData
置き換えることができるようです。

このViewModelを使う形に置き換えると、
データベースの操作と、UIの操作を分けることができるようになるようです。

また、ViewModelを使えば、デバイスの状態が変わった時にデータが不意に消えてしまう事がなくなります。

そして、データベースが変更された時には、
1. RoomからLiveDataに通知が行く
2. LiveDataから更新されたデータを元に、UIが更新される
という流れで、画面表示を変えることができるようになるようです。
先ほどのページの、Replacing Loaders with ViewModel の図参照)

ViewModelではKotlinコルーチンも使える

ViewModelは、Kotlinコルーチンのサポートを含むということでした。

詳しくは、Use Kotlin coroutines with Android Architecture Componentsを参照してください、という事です。

上記リンクには、ViewModelの事、LiveDataの事、Roomの事も書かれているようだったので、
こちらもザックリとメモしてみました。
Kotlinのコルーチンと、ViewModelScope・LifecycleScope・LiveDataなど(アプリリ)
もし興味がありましたら、見てみてください。

更に詳しい情報や、サンプルも。

もっと詳しく知りたい場合は、

先ほどのページの、
Further information にも情報が書かれていたり、

Additional resourcesには
サンプルコードや関連ブログなどがまとめられていたりするので、

そちらも見てみてくださいー。

スポンサーリンク