LoginSignup
3
2

More than 1 year has passed since last update.

【Jetpack Compose】変数宣言の方法とViewを更新する方法

Last updated at Posted at 2022-11-28

概要

Jetpack Compose を使う上で基本となる、 View の状態を表す変数の宣言方法とそれを使った View の更新方法について説明します。

詳しい仕組みについては他の方が書かれていますので、あくまで個人的な備忘録として残したいと思います。

環境

Android Studio version: Android Studio Dolphin 2021.3.1 Patch 1
kotlin version: 1.7.10
compose version: 1.3.1

内容

Jetpack Compose を使って作成した View は Compose によってその状態を常に監視されており、状態が変化すると変化した部分を再描画する処理が行われます。
これを再コンポーズといい、Jetpack Compose で作成した View を更新する手段となっています。

View の状態監視には State 型または MutableState 型の変数が用いられます。

この変数の宣言方法は大きく分けて3パターン存在し、定義方法に応じて View を更新する処理の書き方も異なります。

これらのパターンについて、ボタンをクリックしてテキストを更新するサンプルを用いて説明します。

アプリの見た目はこんな感じです。
Screenshot_20221128_115312_2.png

パターン1: 「=」で変数を宣言

MainActivity.kt
@Composable
fun Greeting() {
    // 「=」で変数を宣言
    val message = remember {
        mutableStateOf("")
    }
    Column() {
        Text(text = "Hello ${message.value}")
        Button(onClick = {
            // コールバック関数から更新
            message.value = "world from Android"
        }) {
            Text("Button1")
        }
        Button(onClick = {
            // オリジナルの関数から更新
            changeText(message)
        }) {
            Text("Button2")
        }

    }
}

private fun changeText(message: MutableState<String>) {
    message.value = "world from Compose"
}

View の状態を表す変数を「=」で宣言するパターンです。
今回は message という名前の変数を用います。
これは View に表示するテキストの一部です。
ここに mutableStateOf 関数で作成した MutableState<String> 型のデータを代入します。
直接代入せずに remember 関数を用いることで変数の値がメモリに保存され、再コンポーズ時に更新後の値と保存した値との差分があれば新しい値に置き換えられます。

また、View 更新するには変数の value プロパティを更新します。

MainActivity.kt
message.value = "world from Android"

この更新処理は onClick などのコールバック関数から行えるほか、そこからさらに自分で定義した関数(上のコードでは changeText 関数)を呼び出して行うこともできます。

MainActivity.kt
 Button(onClick = {
    // オリジナルの関数から更新
    changeText(message)
}) {
...

private fun changeText(message: MutableState<String>) {
    message.value = "world from Compose"
}

なお、この関数は Composable である必要はありません。

パターン2: 「by」で変数を宣言

MainActivity.kt
@Composable
fun Greeting() {
// 「by」で変数を宣言
var message by remember {
    mutableStateOf("")
}
Column() {
    Text(text = "Hello $message")
    Button(onClick = {
        // コールバック関数から更新
        message = "world from Android"
    }) {
        Text("Button1")
    }
    Button(onClick = {
        // オリジナルの関数から更新
        changeText { msg -> message = msg }
    }) {
        Text("Button2")
    }

}

private fun changeText (onNext: (msg: String) -> Unit) {
    onNext("world from compose")
}

by を用いて message 変数を委譲プロパティとして宣言するパターンです。
この場合 message 変数は MutableState<String> 型ではなく String 型となります。

そのため View を更新する場合は value プロパティではなく変数自体の値を更新します。

MainActivity.kt
message = "world from Android"

この方法はパターン1と比べて value プロパティを都度参照する必要がなくコードを簡略化できるというメリットがあります。

オリジナル関数を呼び出して View を更新する場合は、引数に変数の値を更新する処理を入れた関数を渡す必要があります。

MainActivity.kt
Button(onClick = {
    // オリジナルの関数から更新
    changeText { msg -> message = msg }
}) {
...

private fun changeText (onNext: (msg: String) -> Unit) {
    onNext("world from compose")
}

この場合はパターン1と比べて少し複雑になります。

パターン3: 変数を分解宣言

MainActivity.kt
@Composable
fun Greeting() {
    // 分解宣言
    val (message, setMessage) = remember { mutableStateOf("") }
    Column() {
        Text(text = "Hello $message")
        Button(onClick = {
            // コールバック関数から更新
            setMessage("world from Android")
        }) {
            Text("Button1")
        }
        Button(onClick = {
            // オリジナルの関数から更新
            changeText { msg -> setMessage(msg) }
        }) {
            Text("Button2")
        }

    }
}

private fun changeText (onNext: (msg: String) -> Unit) {
    onNext("world from Compose")
}

変数を分解宣言する方法です。
一つ目の変数はゲッター、二つ目の変数はセッターになります。
そのため、変数の値を参照するときは message をそのまま参照し、変数の値を更新するときは setMessage("world from Android") のようにして値をセットします。

オリジナルの関数から更新するときは二つ目の変数を用いてパターン2と同じように書きます。

まとめ

ここまで変数宣言の3つのパターンと更新方法について書きました。
公式ドキュメントによると、状況に応じて3つのパターンからわかりやすいものを選んで書けば良いとのことです。
個人的には慣れるまでは直感的に書けるパターン1を使うことが多くなりそうな気がします。

また、最後に変数の値を更新しても View が更新されないNGパターンをいくつかご紹介します。

NGパターン

変数がMutableState型ではない

MainActivity.kt
@Composable
fun Greeting() {
    var message = ""
    Column() {
        Text(text = "Hello $message")
        Button(onClick = {
            // コールバック関数から更新
            message = "world from Android"
        }) {
            Text("Button1")
        }
        Button(onClick = {
            // オリジナルの関数から更新
            changeText { msg -> message = msg }
        }) {
            Text("Button2")
        }
    }
}

この場合 message 変数はただの String 型であるため Compose によって監視されず、再コンポーズの際に差分のチェックが行われません。

なお、以下のように remember 関数を用いて定義した場合も同様です。

MainActivity.kt
@Composable
fun Greeting() {
    var message = remember { "world" }
    Column() {
        Text(text = "Hello $message")
        Button(onClick = {
            // コールバック関数から更新
            message = "world from Android"
        }) {
            Text("Button1")
        }
        Button(onClick = {
            // オリジナルの関数から更新
            changeText { msg -> message = msg }
        }) {
            Text("Button2")
        }
    }
}

remember 関数を使っていない

MainActivity.kt
@Composable
fun Greeting() {
    val message = mutableStateOf("")
    Column() {
        Text(text = "Hello ${message.value}")
        Button(onClick = {
            // コールバック関数から更新
            message.value = "world from Android"
        }) {
            Text("Button1")
        }
        Button(onClick = {
            // オリジナルの関数から更新
            changeText(message)
        }) {
            Text("Button2")
        }

    }
}

変数は MutableState 型ではありますが、remember 関数を使っていないため再コンポーズの際に変数の値が初期化され、View の表示も変わりません。

Composable関数のスコープ内部から呼び出している

MainActivity.kt
@Composable
fun Greeting() {
    // 「=」で変数を定義
    val message = remember {
        mutableStateOf("")
    }
    Column() {
        Text(message = "Hello ${message.value}")
        Button(onClick = {
            // コールバック関数から更新
            message.value = "world from Android"
        }) {
            Text("Button1")
        }
        Button(onClick = {
            // オリジナルの関数から更新
            changeText(message)
        }) {
            Text("Button2")
        }

    }
    // Composable関数内部から直接呼出し・・・(A)
    changeText2(message)
}

private fun changeText(message: MutableState<String>) {
    message.value = "world from Compose"
}

private fun changeText2 (message: MutableState<String>) {
    message.value = "world from Composable"
}

Composable 関数のスコープ内部から変数を更新しても変数の変更は無視され、View には反映されません。
これは上のコードのように Composable 関数の内部から別の関数を呼び出して更新する場合でも同じです。((A)の部分)
これは、Greeting 関数が呼ばれる→message.value が更新される→再コンポジションが発生する→再度 Greeting 関数が呼ばれるという無限ループを回避するためのようです。

なお、ButtononClickComposable 関数のスコープ外であるため問題ないようです。

参考

状態と Jetpack Compose  |  Android Developers
Jetpack Compose State Practices - Qiita

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2