【正社員】還元率83%【フリーランス】マージン一律5万円で案件をご紹介させていただきます。 詳細はこちら

【Android】<Kotlin>Jetpack Composeで状態管理を理解する:mutableStateOf・remember・ViewModel・StateFlowの違いと使い分け

【Android】<Kotlin>Jetpack Composeで状態管理を理解する:mutableStateOf・remember・ViewModel・StateFlowの違いと使い分け
すだ

みなさまこんにちは〜!
メモリアインクのすだです。

本日は、
Jetpack ComposeでUIの実装を行うにあたってほぼ必ず必要になってくる「状態管理」について わかりやすく解説していきます。

この記事を読んでわかること…
・Jetpack Composeにおける「状態管理」とは
・mutableStateOf、rememberの使い方
・ViewModelの使い方
・StateFlowの使い方

目次

環境

  • Kotlin (ver 1.9.0)
  • Android Studio (Giraffe | 2022.3.1 Patch 3)

Jetpack Composeでの状態管理とは

アプリを作るとき、「今どんな状態か」を覚えておくことはとても大事です。
状態が変われば、画面も変わる。
その “状態” をどこで、どうやって持つかが「状態管理」です。

Compose では UI を状態に基づいて “宣言的に” 描画します。そのため、状態の管理方法を誤ると、意図しない再描画やバグの温床になってしまいます。

この記事では、Compose における状態管理の基本から応用までを段階的に解説し、mutableStateOfrememberViewModelStateFlowrememberSaveable の違いと使い分けを実践的なコードサンプルとともに紹介します。

mutableStateOf と remember での状態管理

mutableStateOfとは?

mutableStateOf は、Compose における状態ホルダーです。
値が更新されると、それを使用している Composable が自動的に再描画されます。

val text = mutableStateOf("")

これは State<String> 型の変数 text を作るもので、
この text.value を変更すると、それを使っている UI が 自動で再描画されるのがポイントです。

rememberとは?

remember は、Composable 関数内で一度だけ初期化され、再Composeのたびに値を再利用できるようにするためのラッパーです。(@Composable 関数内でしか使えない)

var text by remember { mutableStateOf("") }

by を使うことで、text.value と書かずに text だけでアクセスできます。
これを使うと、再Compose(画面の再描画)が起こっても mutableStateOf("") がもう一度呼ばれないようになります。(つまり、「状態を保ったままUIだけ描き直す」ことができる。)

mutableStateOf × remember の利用

mutableStateOf は「状態そのものを作る」もの
remember は「その状態をComposable内で覚えておく」もの

つまり、「箱を作るのが mutableStateOf」で「その箱を忘れないようにするのが remember」です。

以下は具体的なコード例です。

@Composable
fun GreetingForm() {
    var name by remember { mutableStateOf("") }

    Column(modifier = Modifier.padding(16.dp)) {
        OutlinedTextField(
            value = name,
            onValueChange = { name = it },
            label = { Text("お名前") }
        )

        Spacer(modifier = Modifier.height(8.dp))

        Text("こんにちは、$name さん!")
    }
}

上記は、「入力フォーム(テキストボックス)を使って、ユーザーの入力をリアルタイムで var name by remember { mutableStateOf("") }に保持する」というものです。

まずmutableStateOf("")で「文字列を入れる箱」が作られ、rememberによって状態は再描画時にも保持されることが約束されます。
入力すると onValueChange が発火し、name = it によって状態が更新される、そしてComposeが「状態変わった」と検知しUI(Textなど)を自動で更新してくれるという流れです。

注意:

@Composable
fun Example() {
    val text = mutableStateOf("") // ← rememberなし

    OutlinedTextField(
        value = text.value,
        onValueChange = { text.value = it }
    )
}

上記のように、@Composableの中でrememberを使わずにmutableStateOfのみで箱だけ用意した状態だと
Example() が再Composeされるたびに text = mutableStateOf("") が実行され、text は毎回 “” に戻ってしまいます。

mutableStateOf は「状態の箱」だけど、保持するためには remember や ViewModel と一緒に使う必要があります。
そうしないと、再Composeのたびに初期化されてしまいます。

ViewModelで状態を管理

ViewModelとは?

ViewModelも、「画面の中身を持っておく」役割を果たします。
そのため状態管理においてとても有効であるほか、ViewModel を使うことで、「UI」と「データ処理」をキレイに分けることができます。

Composeは画面が再生成(再Compose)されるたびに状態が失われる可能性があります。
ViewModelを使うことで、状態をUIロジックから切り離し、ライフサイクルに強く、再利用性の高い状態管理が可能になります。

class PlanInsertViewModel : ViewModel() {
    val inputText1 = mutableStateOf("")

    fun onText1Change(text: String) {
        inputText1.value = text
    }
}

@Composable
fun PlanInsertScreen(viewModel: PlanInsertViewModel = viewModel()) {
    val text1 by viewModel.inputText1

    Column(modifier = Modifier.padding(16.dp)) {
        OutlinedTextField(value = text1, onValueChange = viewModel::onText1Change, label = { Text("テキスト1") })
    }
}

上記は、「入力フォーム(テキストボックス)を使って、ユーザーの入力をリアルタイムで ViewModel に保持する」というものです。

これは、UI側で状態を持たずにViewModel 側で入力内容(状態)を保持し、
ユーザーが文字を入力すると、即座に ViewModel の状態が更新され、それが UI に反映される(=状態連動型 UI)
という仕組みになっています。

このViewModelでもmutableStateOf("")で状態を入れておく箱を作っていますが
このようにmutableStateOfは@Composable以外の場所でも使用できます。

rememberSaveableでの状態管理

rememberSaveableとは?

rememberSaveable は、Jetpack Compose でアプリの「画面回転にも強い状態管理」をしたいときにとても重要な存在です。

rememberSaveable は画面回転やプロセス復元でも「値を保持」してくれる rememberといえるでしょう。

var text by rememberSaveable { mutableStateOf("") }

rememberとの違い

remember は単に「メモリの中に一時的に保存している」だけですが、

rememberSaveable は内部で SavedInstanceState(Android標準の保存仕組み) を使って
「画面が破棄されても状態をBundleに保存」 → 再生成時に復元してくれます。

そのため、rememberSaveable を使うことで、Compose でも従来の Android の画面状態復元機能を活かすことができるということになります。

だからといって、「必ずrememberSaveableを使えばいい」というわけではありません。

rememberSaveableは内部でBundle(Androidの状態保存機構)を使っているため、
保存対象に制限があります(プリミティブ型、ParcelableSerializable など)
あまり頻繁に使うとパフォーマンスに影響することもあるので、

画面回転されても状態を保持したい場合など、半永続的に保持したい場合のみ使うと良いでしょう。

ViewModel × StateFlowでの状態管理

StateFlowとは?

常に最新の状態を持っていて、それが変わるたびに教えてくれるデータの流れ(Flow)です。
これも状態管理をするうえで非常に有効です。
Composeの場合、 collectAsState() を使うと、UIが勝手に反応して再描画してくれるという仕組みを持っています。

ViewModel × StateFlowの利用

StateFlow と Jetpack Compose を組み合わせた状態管理は、非同期処理・リアクティブなUI・状態の分離など、モダンAndroid開発においてとても重要です。

class UserViewModel : ViewModel() {
    private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
    val uiState = _uiState.asStateFlow()

    fun fetchUser() {
        viewModelScope.launch {
            try {
                delay(1000) // API呼び出しのつもり
                _uiState.value = UiState.Success("田中")
            } catch (e: Exception) {
                _uiState.value = UiState.Error("取得失敗")
            }
        }
    }
}

@Composable
fun UserScreen(viewModel: UserViewModel = viewModel()) {
    val state by viewModel.uiState.collectAsState()

    when (state) {
        is UiState.Loading -> CircularProgressIndicator()
        is UiState.Success -> Text("ようこそ、${(state as UiState.Success).userName} さん!")
        is UiState.Error -> Text("エラー: ${(state as UiState.Error).message}")
    }
}

MutableStateFlowmutableStateOfと同様に状態を保持しておく箱の役割を果たしますが
使い方、向いている場面が異なります。

mutableStateOfが「Compose UI専用の状態」を保持し、シンプルで軽いのに比べて
MutableStateFlowは非同期処理・データの流れにも対応できるため、複雑な状態管理に向いています。

また、asStateFlow() は「中身を変更できる MutableStateFlow を、変更できない StateFlow として公開する」ためのものです。

このようにViewModel側で「StateFlow」を利用して状態管理を行う準備ができたら
@Composable側でcollectAsState()を使って取得するという流れです。

こうすることで、uiState の値が変わったら、自動的に Composeが再Compose(再描画) してくれます。

まとめ

おつかれさまでした。いかがでしたでしょうか!

ComposeでUIを実装する上で、状態管理は非常に大切なポイントなので
それぞれの機能をぜひきちんと理解した上で使い分けてください!

すだ

技術者としてのキャリアパスを次のレベルへと進めたい皆様、
未経験からIT・Webエンジニアを目指すなら【ユニゾンキャリア】
を通じて、
自分の市場価値をさらに向上させてみませんか?

それではまた次回の記事でお会いしましょう!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

弊社テックブログをご愛読いただきありがとうございます。
当テックブログを運用している株式会社メモリアインクは、
【正社員】還元率83%
【フリーランス】マージン一律5万円で案件のご紹介
と、エンジニアの皆様に分かりやすい形で稼げる仕組みを構築し提供させていただいております。

コメント

コメントする

目次