こんにちは。エキサイト株式会社 Androidエンジニアの克です。
今回はJetpack Composeにおける状態ホイスティングの概念についてお話します。
Composeの状態とは
Composeの状態の分類として、Composeの内部で状態を持つ「ステートフルなCompose」と、Composeの内部には状態を持たない「ステートレスなCompose」の2種類が存在します。
例えば下記のようなComposeは、内部で状態を持っているためステートフルとなります。
@Composable fun NameInput() { var name by remember { mutableStateOf("") } TextField( value = name, onValueChange = { name = it } ) }
ステートフルなComposeの短所
先に例示したようなステートフルなComposeには、下記のような短所があります。
- 内部に状態を持っているので、外部から状態に干渉できない
- 内部に状態を持っているので、テストが容易ではない
- 状態は変数のため、どこで変化するのかを追う必要がある
例えば上記のNameInput
では、ボタンを押した時にTextField
を空にしたいということはできません。(NameInput
内で実装すれば可能ですが、それはこのComposeの責務を超えてしまいます)
そのため、基本的にはステートレスなComposeの方が取り回しがよく汎用性があります。
このステートフルなComposeをステートレスなComposeに変えるための手法が状態ホイスティングです。
ただし、ステートフルなComposeの短所は長所にも言い換えられます。
- 内部に状態を持っているため、外部からはComposeの状態を気にする必要がない
このため、場合によってはあえてステートフルなComposeとして扱うという選択肢もあるということを留意しておきましょう。
状態ホイスティングとは
ホイスティング(Hoisting)とは「巻き上げ」という意味で、Composeの状態ホイスティングとはすなわち「Composeの状態を外部に巻き上げる」という意味となります。
これは「Comopseの内部には状態を持たない」ようにするのと同義であり、結果としてステートレスなComposeにすることができるというわけです。
上記のステートフルなComposeであるNameInput
に状態ホイスティングを適用したものが下記になります。
@Composable fun NameInput(name: String, onNameChange: (String) -> Unit) { TextField( value = name, onValueChange = onNameChange ) } @Composable fun NameScreen() { var name by remember { mutableStateOf("") } NameInput( name = name, onNameChange = { name = it } ) }
状態ホイスティングとは具体的には、Composeが使用する値およびComposeで発生するイベントを外部に巻き上げることを指します。
上記のコードにおいてはTextFieldに表示する value
が使用する値、TextFieldで発生する onValueChange
が発生するイベントです。
こうすることでステートフルなComposeにあった短所を解消することができました。
- 内部に状態を持たないので、外部から状態を変更できる
- 内部に状態を持たないので、テストが容易である
- 状態は不変なため、変更されることはない
例えばボタンを押した時にTextField
を空にしたい場合は下記のような実装になります。
@Composable fun NameScreen() { var name by remember { mutableStateOf("") } NameInput( name = name, onNameChange = { name = it } ) Button(onClick = { name = "" }) { Text(text = "削除") } }
最後に
Jetpack Composeは宣言型UIの思想で作られているため、カプセル化や状態ホイスティングによって責務の分離が行いやすく、部品としての管理がしやすくなっています。
再利用性も高く、不具合の生まれる余地を減らしながら効率的に画面の構築を行うことができます。
既存のレイアウトxmlとは概念が大きく変わりはしますが、Jetpack Composeはこういった面でのメリットが大きいのでぜひ使っていきたいですね。