
みなさまこんにちは〜!
メモリアインクのすだです。
本日は、
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 における状態管理の基本から応用までを段階的に解説し、mutableStateOf
、remember
、ViewModel
、StateFlow
、rememberSaveable
の違いと使い分けを実践的なコードサンプルとともに紹介します。
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の状態保存機構)を使っているため、
保存対象に制限があります(プリミティブ型、Parcelable
、Serializable
など)
あまり頻繁に使うとパフォーマンスに影響することもあるので、
画面回転されても状態を保持したい場合など、半永続的に保持したい場合のみ使うと良いでしょう。
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}")
}
}
MutableStateFlow
はmutableStateOf
と同様に状態を保持しておく箱の役割を果たしますが
使い方、向いている場面が異なります。
mutableStateOf
が「Compose UI専用の状態」を保持し、シンプルで軽いのに比べてMutableStateFlow
は非同期処理・データの流れにも対応できるため、複雑な状態管理に向いています。
また、asStateFlow()
は「中身を変更できる MutableStateFlow
を、変更できない StateFlow
として公開する」ためのものです。
このようにViewModel側で「StateFlow」を利用して状態管理を行う準備ができたら
@Composable側でcollectAsState()
を使って取得するという流れです。
こうすることで、uiState
の値が変わったら、自動的に Composeが再Compose(再描画) してくれます。
まとめ
おつかれさまでした。いかがでしたでしょうか!
ComposeでUIを実装する上で、状態管理は非常に大切なポイントなので
それぞれの機能をぜひきちんと理解した上で使い分けてください!



技術者としてのキャリアパスを次のレベルへと進めたい皆様、
<未経験からIT・Webエンジニアを目指すなら【ユニゾンキャリア】>
自分の市場価値をさらに向上させてみませんか?
それではまた次回の記事でお会いしましょう!
コメント