Jetpack Composeで状態を扱う
Overview
アプリにおいて状態とは、時間とともに変化する可能性がある値すべてを指します。これは非常に広範な定義であり、Room データベースにも、クラス内の変数一つにも当てはまります。
すべての Android アプリはユーザーに状態を表示します。Android アプリの状態の例を次にいくつか示します。
ネットワーク接続を確立できないときに表示されるスナックバー。
ブログ投稿と関連コメント。
ユーザーがボタンをクリックしたときに再生されるボタンの波紋アニメーション。
ユーザーが画像の上に描画できるステッカー。
Jetpack Compose では、Android アプリが状態をどこで、どのように保存し、使用するかの設定を明示的に行うことができます。このガイドでは、状態とコンポーザブルの関係に加え、Jetpack Compose が提供する、状態を簡単に処理するための API を中心に説明します。
今回は、MutableStateを使って状態を扱い、カウンターが増えるロジックを作ってみます。
summary
公式によると
コンポーザブル内の状態
コンポーズ可能な関数は、remember API を使用してオブジェクトをメモリに格納できます。初回コンポーズの際に、remember によって計算された値がコンポジションに保存され、保存された値は再コンポーズの際に返されます。remember を使用すると、可変オブジェクトと不変オブジェクトの両方を保存できます。
mutableStateOfを使用するには、rememberで囲む必要があります。
値にアクセスするときは、.valueを使用します。
全体のコード
package com.example.basiccodelab
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.tooling.preview.Preview
import com.example.basiccodelab.ui.theme.BasicCodelabTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
BasicCodelabTheme {
// A surface container using the 'background' color from the theme
Surface(
color = MaterialTheme.colorScheme.background
) {
Greeting(name = "Android")
}
}
}
}
}
@Composable
fun Greeting(name: String) {
// rememberで、mutableStateOfを使うと、値の変更をComposableが再描画する
var counter = remember {
mutableStateOf(0)
}
Button(onClick = {
// .valueで値を取得、.value = で値を変更
counter.value++
}) {
// Textの引数には、Int型のcounter.valueを渡している
Text(text = "I've been clicked ${counter.value} times")
}
}
@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
BasicCodelabTheme {
Greeting(name = "Android")
}
}
valueを使わない方法
変数の =
を by
に変更して、赤い文字のところをoption + Enterを押して、setValue
とgetValue
をimportすると、valueを使わなくても状態を操作できるようになります。
// rememberで、mutableStateOfを使うと、値の変更をComposableが再描画する
var counter by remember {
mutableStateOf(0)
}
Button(onClick = {
// .valueで値を取得、.value = で値を変更
counter++
}) {
// Textの引数には、Int型のcounter.valueを渡している
Text(text = "I've been clicked ${counter} times")
}
valueを使用しない全体のコード
package com.example.basiccodelab
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.tooling.preview.Preview
import com.example.basiccodelab.ui.theme.BasicCodelabTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
BasicCodelabTheme {
// A surface container using the 'background' color from the theme
Surface(
color = MaterialTheme.colorScheme.background
) {
Greeting(name = "Android")
}
}
}
}
}
@Composable
fun Greeting(name: String) {
// rememberで、mutableStateOfを使うと、値の変更をComposableが再描画する
var counter by remember {
mutableStateOf(0)
}
Button(onClick = {
// .valueで値を取得、.value = で値を変更
counter++
}) {
// Textの引数には、Int型のcounter.valueを渡している
Text(text = "I've been clicked ${counter} times")
}
}
@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
BasicCodelabTheme {
Greeting(name = "Android")
}
}
数字が増減するボタンを作った
ボタンをクリックすると、数字が増えたり、減ったりするボタンを作ってみました。
package com.example.basiccodelab
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.example.basiccodelab.ui.theme.BasicCodelabTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
BasicCodelabTheme {
// A surface container using the 'background' color from the theme
Surface(
color = MaterialTheme.colorScheme.background
) {
Greeting(name = "Android")
}
}
}
}
}
@Composable
fun Greeting(name: String) {
Column {
// rememberで、mutableStateOfを使うと、値の変更をComposableが再描画する
var counter by remember {
mutableStateOf(0)
}
Button(onClick = {
// .valueで値を取得、.value = で値を変更
counter++
}) {
// Textの引数には、Int型のcounter.valueを渡している
Text(text = "数字が増える: ${counter}")
}
Spacer(modifier = androidx.compose.ui.Modifier.height(16.dp))
Button(onClick = {
// .valueで値を取得、.value = で値を変更
counter--
}) {
// Textの引数には、Int型のcounter.valueを渡している
Text(text = "数字が減る: ${counter}")
}
}
}
@Preview(showBackground = true)
@Composable
fun DefaultPreview() {
BasicCodelabTheme {
Greeting(name = "Android")
}
}
プラス
マイナス
thoughts
今回、MutableStateを使ってみた感想ですが、FlutterのsetState、ReactのuseStateと同じように、状態を保持して、操作することができる機能があるのだなと、理解できました。
最近の技術は概念が似ているので、理解しやすいですね。やっていることは同じ。
学習の参考になった動画
Discussion