【Kotlin/Android Studio】ViewModelの使い方!画面再構築のデータ保持

【Kotlin/Android Studio】ViewModelの使い方!画面再構築のデータ保持

この記事からわかること

  • Android StudioViewModel利用する方法
  • 画面再構築される際にデータ保持するには?
  • ViewModelのインスタンス化方法やライフサイクルスコープとは?
  • by viewModels()導入

index

[open]

\ アプリをリリースしました /

みんなの誕生日

友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-

posted withアプリーチ

参考文献:公式リファレンス:ViewModel

環境

AndroidアプリのUI状態の保存

公式リファレンス:UI の状態を保存する

Androidは画面の回転など特定の動作などによりシステムによってActivityやアプリが破棄されデータが消失してしまうことが多く発生します。そのためUIの状態を一時的に保存して復元するような仕組みを構築しておくことがユーザーエクスペリエンスを維持する上で重要になってきます。

UIの状態を一時的に保存するための仕組みは以下のようにいくつか用意されていますがユースケースに応じて最適なものを取捨選択する必要があります。

※ Saved instance stateは基本的にユーザーの入力やナビゲーションに依存する一時的な状態などを保持するために使用します。そのため軽量なデータのみとし大量のデータの場合はPersistent storage(永続化ストレージ)を使用することが推奨されています。Saved instance stateは以下の種類があります。

  1. Jetpack Compose: rememberSaveable・・・Compose用
  2. View: onSaveInstanceState() API・・・主にUIロジックを保存
  3. ViewModel: SavedStateHandle・・・主にビジネスロジックを保存

ユーザーの期待値との乖離

実際にユーザーの期待とアプリの挙動が乖離を生まないようにするためにはユーザーの期待値とシステムの挙動を理解しておく必要があります。例えば以下のような動作を行った場合にはアプリはクリーンな状態から起動され直されることが期待値になり、以下の挙動ではシステムの挙動も一致してUIの状態をクリーンにします。

ユーザーがActivityを完全に終了する方法

しかしユーザーがクリーンになることを期待しない場合でもシステムがUIの状態を破棄してしまうケースが存在します。

期待値に反してシステムがUIの状態を破棄してしまうケース

これに応じたそれぞれの対応の違いは以下になります。

ViewModel Saved instance state Persistent storage(永続ストレージ)
保存先 メモリ内 メモリ内 ディスクまたはネットワーク上
構成変更後も保持
システムによるプロセス終了後も保持 ×
ユーザーによるアクティビティ終了/onFinish() も保持 × ×
データの上限 複雑なオブジェクトは問題ないが、使用可能なメモリによってスペースが制限される プリミティブ型と String などのシンプルな小さなオブジェクトのみ ネットワーク リソースからのディスク スペースまたはネットワークからの取得コスト / 時間によってのみ制限される
読み取り / 書き込み時間 高速(メモリアクセスのみ) 低速(シリアル化 / シリアル化解除が必要) 低速(ディスク アクセスまたはネットワーク トランザクションが必要)

今回はその中の1つである「ViewModel」の使い方についてまとめていきます。

ViewModelとは?

Androidアプリ開発の「ViewModel」はJetPack Componentの1つとして提供されているUI状態を保持する機能です。ViewModelを使用することでデータやUI状態などがキャッシュされ、再構築後もまた同じ画面に復帰させることができるようになります。

ライフサイクルとスコープ

ViewModelのライフサイクル(インスタンスの生成→破棄)は依存しているスコープに影響します。ここでいうスコープはViewModelStoreOwnerクラスと呼ばれ、主にActivityやFragmentなどがオーナーになります。そのViewModelStoreOwnerが消えるとき(Activityなら終了時、Fragmentならデタッチ時)までViewModelはメモリに情報を保持します。

基本的な役割はアーキテクチャでよく聞くMVVMのVM(ViewModel)と同じです。

実装方法

最新のAndroid Studioであればプロジェクト内で使用するために必要な導入作業は必要ありません。今回は「ボタンクリックで2つのカウンターが変化する」アプリを作っていきます。まずはViewModelの挙動がわかりやすいようにUIを構築しておきます。またプロジェクトの全体はGitHubに上げていますので参考にしてください。


<TextView
  android:id="@+id/viewmodel_text"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:text="0"
  app:layout_constraintBottom_toBottomOf="parent"
  app:layout_constraintEnd_toEndOf="parent"
  app:layout_constraintStart_toStartOf="parent"
  app:layout_constraintTop_toTopOf="parent" />

<Button
  android:id="@+id/plus_button"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:text="plus"
  app:layout_constraintBottom_toBottomOf="parent"
  app:layout_constraintEnd_toEndOf="parent"
  app:layout_constraintStart_toStartOf="parent"
  app:layout_constraintTop_toBottomOf="@+id/viewmodel_text" />

<TextView
  android:id="@+id/plane_text"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:text="0"
  app:layout_constraintBottom_toTopOf="@+id/viewmodel_text"
  app:layout_constraintEnd_toEndOf="parent"
  app:layout_constraintStart_toStartOf="parent"
  app:layout_constraintTop_toTopOf="parent" />

ViewModelクラスの実装

続いて独自のViewModelクラスを定義します。中身はシンプルにカウンターのデータを保持するcountプロパティとインクリメントするためのplusメソッドです。


import androidx.lifecycle.ViewModel

class MyViewModel:ViewModel() {

    public var count:Int = 0

    fun plus(){
        count++
    }
}

MainActivityからViewModelを取得する

MainActivityからViewModelを取得して画面に反映させていきます。ここで注意なのはViewModelは直接インスタンス化せずにViewModelProviderクラスを介してインスタンス化する必要があります。これはViewModelProviderがActivityやFragmentのライフサイクルに合わせてViewModelを生成・破棄する役割を持っているためです。つまりViewModelのライフサイクルが適切に管理するためということです。


val viewModel: MyViewModel = ViewModelProvider(this)[MyViewModel::class.java]

インスタンス化できたらあとは以下のようにカウンターを操作できるようにしておきます。


var planeCounter = 0
  
val planeText:TextView = findViewById(R.id.plane_text)
val vmText:TextView = findViewById(R.id.viewmodel_text)

val button:Button = findViewById(R.id.plus_button)
button.setOnClickListener {

    planeCounter++
    viewModel.plus()

    planeText.setText(planeCounter.toString())
    vmText.setText(viewModel.count.toString())
}

ビルドしてみるとボタンをクリックすると2つのカウンターが動作するのを確認できます。

【Kotlin/Android Studio】ViewModelの使い方!画面再構築のデータ保持

画面を横向きにしてみます。両方のカウンターが0になってしまいました。実はこれは正常でありもう一度ボタンを押してみてください。

【Kotlin/Android Studio】ViewModelの使い方!画面再構築のデータ保持

するとViewModelを介している方は縦の続きからカウントが増えていっていることがわかります。このようにViewModelではデータをキャッシュすることはできますが再構築時にすぐにUIに反映させることはできないようです。

【Kotlin/Android Studio】ViewModelの使い方!画面再構築のデータ保持

再構築時にすぐにUIに反映させたい場合はLiveDataを組み合わせます。

ViewModelの初期化方法

ViewModelはライフサイクルに依存して管理する必要があるため普通にインスタンス化するのではなくViewModelProviderを使用してインスタンス化しました。


val viewModel: MyViewModel = ViewModelProvider(this)[MyViewModel::class.java]

しかしViewModel自体が引数を受け取る場合は専用のファクトリーメソッドを実装する必要があります。


class MyViewModel(private val repository: RootRepository): ViewModel() {
  // 〜〜〜〜〜〜〜〜〜〜〜
}
class MyViewModelFactory(private val repository: RootRepository): ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(MyViewModel::class.java)) {
            @Suppress("UNCHECKED_CAST")
            return MyViewModel(repository) as T
        }
        throw java.lang.IllegalArgumentException("Unknown ViweModel class")
    }
}

おすすめ記事:【Kotlin/Android Studio】Factory Methodデザインパターンとは?


private val viewModel: MyViewModel by viewModels {
    MyViewModelFactory(MyRepository())
}

by viewModels()を導入する

引数がある際にファクトリーメソッドを実装するのは大変なのでそれを実装せずともby viewModels()を使用してインスタンス化させることができるようになっています。


private val viewModel: MainViewModel by viewModels()

by viewModels()を使用するためには「build.gradle(Module)」ファイルに以下を追加します。


implementation 'androidx.fragment:fragment-ktx:1.2.2'

ViewModelクラスのインスタンス化タイミング

ViewModelクラスが実際にインスタンス化されるタイミングはFragemntの初期化時ではなくViewModelを参照したときになります。そのためViewModel内のイニシャライザなどに処理を実装していても参照タイミングになるので注意してください。

private val viewModel: MainViewModel by viewModels()

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

ご覧いただきありがとうございました。

Search Box

Sponsor

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article

index