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

この記事からわかること
- Android StudioのViewModelを利用する方法
- 画面が再構築される際にデータを保持するには?
- ViewModelのインスタンス化方法やライフサイクル、スコープとは?
index
[open]
\ アプリをリリースしました /
参考文献:公式リファレンス:ViewModel
環境
- Android Studio:Flamingo
- Kotlin:1.8.20
ViewModelとは?
Androidアプリ開発のViewModelはJetPackのコンポーネントの1つとして提供されているUI状態を保持する機能です。Androidではアプリの画面が縦型から横向きに変更になる際に画面が再構築されるという特徴があります。この際にデータやUI状態などが一度リセットされてしまうため同じ画面を表示することができない場合があります。
ViewModelを使用することでデータやUI状態などがキャッシュされ、再構築後もまた同じ画面に復帰させることができるようになります。
ライフサイクルとスコープ
ViewModelのライフサイクル(インスタンスの生成→破棄)は依存しているスコープに影響します。ここでいうスコープはViewModelStoreOwner
クラスと呼ばれ、主にActivityやFragmentなどがオーナーになります。そのViewModelStoreOwner
が消えるとき(Activityなら終了時、Fragmentならデタッチ時)までViewModelはメモリに情報を保持します。
基本的な役割はアーキテクチャでよく聞くMVVMのVM(ViewModel)と同じです。
実装方法
最新のAndroid Studio:Flamingoであればプロジェクト内で使用するために必要な導入作業は必要ありません。
またプロジェクトの全体はGitHubに上げていますので参考にしてください。
今回は「ボタンクリックで2つのカウンターが変化する」アプリを作っていきます。まずはViewModelの挙動がわかりやすいようにUIを構築しておきます。
<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 vm: 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++
vm.plus()
planeText.setText(planeCounter.toString())
vmText.setText(vm.count.toString())
}
ビルドしてみるとボタンをクリックすると2つのカウンターが動作するのを確認できます。

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

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

再構築時にすぐにUIに反映させたい場合はLiveDataを組み合わせます。
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。
個人開発に限界を感じたらiOSに特化したプログラミングスクール「iOSアカデミア」も検討してみてください!無料相談可能で「最短・最速」でiOSエンジニアになれるように手助けしてくれます。