2. Composable 状態 と 管理 について(remember rememberSaveable ViewModel) - Ki-Kobayashi/Android-Wiki GitHub Wiki
さまざまなパターンのアーキテクチャによるAndroidアプリ(kotlin)の実装例を格納したGoogle公式リポジトリ
android/architecture-samples UI・マテリアルデザインのサンプルアプリ
android/compose-samples Jetpackを利用したサンプル
android/sunflower 上級編 android/architecture-components-samples/GithubBrowserSample/ ネットワークへのアクセスや認証など、実際にストアで公開されるアプリに近い機能を備えたアプリ
.
Material Design (3) androidx.compose.material3
.
アーキテクチャ、UI(Jetpack Compose)の参考など kliment-jonceski/JetPackPlaygroundApp
特定のアクションをリクエストする時に使用できる、メッセージングオブジェクトのこと。
例えば別の画面を開きたい時に、「開いている画面に対して別の画面を開いてください」とアプリにメッセージとして伝えることで、別の画面を開ける。
今回は基本的な使い方として、以下がある
- 「Activityを起動する」
- 「Serviceを起動する」
- 「ブロードキャストを配信する」
【Androidアプリ開発】Intentとは、Intentの使い方とは。
.
Android版の flutter, SwiftUIとでもいうべき、宣言的に簡潔にUIが記述できるライブラリ。https://qiita.com/kota_2402/items/7bbdd87be8024785e25b Compose でのレイアウト
@Composableアノテーションのついた関数によって、宣言的にUIを構築する。
バージョン2と3があるが(2023/7現在)、新しくアプリを作り始めるのであれば最新版を選択すべきだろう。
Compose でマテリアル 2 からマテリアル 3 に移行する
.
-
何か一つのパラメータが変更されたからといって全ての @Composable関数 が再描画されるわけではない。
→ それだと膨大なコストがかかってしまう。 -
一部の @Composable 関数のみが再描画されることがありうる。
→ それどころか、一つの@Composable関数の中のUI部品のうち、必要なUI部品のみが再描画の対象となり、他の部分は再描画されないということさえありうる。 -
再描画が終わらないうちにもう一度再描画の指示がされた場合は、終わっていなかった再描画はキャンセルされる。
-
場合によっては、@Composable関数が UI アニメーションのフレームごとに実行され、一秒間に数百回も実行される可能性がある。
→ パフォーマンスが悪化する。
.
例えば、共有の領域にある変数を変更するなど。
そのような副作用が必要な場合は、@Composable関数のなかで直接行うのではなく、
.
onClick などのコールバックを @Composable関数 に渡して行うようにする。
.
@Composable
@Deprecated("Example with bug")
fun ListWithBug(myList: List<String>) {
var items = 0
Row(horizontalArrangement = Arrangement.SpaceBetween) {
Column {
for (item in myList) {
Text("Item: $item")
items++ // 🛑【NG】避ける!カラム再構成の副作用
👆remember または rememberAsSaveableという関数を用いて、
MutablState<T>という型の変数を設ければ、@Composable関数の中で状態管理をしても良い。
}
}
Text("Count: $items")
}
}
.
@Composable
fun EditTaskScreen(
// TODO:ViewModelは基本外から注入、Nav関連もMyApp(NavHostの定義場所)で制御するようにする
viewModel: EditTaskViewModel,
back: () -> Unit,
) {
// private な Composable は部品として使いまわせるようにしとく
// → 処理(✅ callback )は public な Composable から渡すようにする
// → ViewModelは、MyAppでさらに外部から渡すようにする(一元管理できるから?)
// .
// 【collectAsState】 : StateFlow や Flow からデータを収集し、そのデータを ComposeUI の State オブジェクトに変換。
// これにより、Compose が状態の変更を検知して UI を再描画できるようになる
val uiState by viewModel.uiState.collectAsState()
// uiState を渡して、変化があれば再コンポーズさせる?
EditTaskScreen(
uiState = uiState,
back = back,
loadData = { viewModel.loadData() },
changeIdle = { viewModel.changeIdle() },
updateTask = { title, description ->
viewModel.updateTask(title, description)
},
showDeleteDialog = {
viewModel.showDeleteDialog()
},
deleteTask = {
viewModel.deleteTask()
}
)
}
.
- ステートフル: @Composable関数内部で、rememberなどを用いて状態を持っている
- ステートレス: @Composable関数内部では状態を持たず、むしろ関数への引数として必要な値やクロージャーを渡して運用 (例えばViewModelなど外側で状態管理など)
慣習として、表示内容は同じだが、ステートレスとステートフルな関数2つを用意する場合がある。↓
@Composable
fun HelloScreen() {
var name by rememberSaveable { mutableStateOf("") }
HelloContent(name = name, onNameChange = { name = it })
}
@Composable
fun HelloContent(name: String, onNameChange: (String) -> Unit) {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = "Hello, $name",
modifier = Modifier.padding(bottom = 8.dp),
style = MaterialTheme.typography.h5
)
OutlinedTextField(
value = name,
onValueChange = onNameChange,
label = { Text("Name") }
)
}
}
.
💡remember系 の 補足💡
・「by」で書くと、.valueに直接アクセスできる(titleだけで中身の取得が可能)。
・「=」で書くと、title.valueのように、「.value」とつけないと中身にアクセスできない
→ メモリに一時保存し、データの変化を監視
→ Activity の破棄(画面回転、ダーク/ライトモード切替、言語切替)で、データも破棄される
.
→ Bundleに保存し、データの変化を監視
→ Bundle のため、シンプルなデータしか保存できない 👉複雑なデータなら次項のViewModelを使用する
💡【※】
→ Activity が破棄されてもデータを復元できる
. ### 💎 【※】rememberSaveable に保存できる型 Bundleに保存できる型が該当。具体的には以下。
- 基本データ型
- 配列
- listOf / HashMap (🛑 不変な型でないと、変更時に再コンポーズをトリガーできない)
- Parcelable オブジェクト
- Serializable オブジェクト
. ### 💎 【※】rememberSaveable に複雑な値も保存するには?
.
private val _uiState = MutableStateFlow<UiState>(UiState.Initial)
// TODO: asStateFlow: MutableStateFlow → StateFlowに変換
val uiState: StateFlow<UiState> = _uiState.asStateFlow()
.
var inputMessage by mutableStateOf("")
private set
.
.
. 状態監視している値が変更されると、監視している Conposable で 再コンポジション される
.
val mutableState = remember { mutableStateOf(default) }
// or
var value by remember { mutableStateOf(default) }
// or
val (value, setValue) = remember { mutableStateOf(default) }
remember関数を用いて、MutableStateという型の状態nameを保持。
.
@Composable
fun HelloScreen() {
var name by rememberSaveable { mutableStateOf("") }
HelloContent(name = name, onNameChange = { name = it })
}
@Composable
fun HelloContent(name: String, onNameChange: (String) -> Unit) {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = "Hello, $name",
modifier = Modifier.padding(bottom = 8.dp),
style = MaterialTheme.typography.h5
)
OutlinedTextField(
value = name,
onValueChange = onNameChange,
label = { Text("Name") }
)
}
}
また、Androidで用いられるオブザーバブルな型から値を持ってきて、Composeで使うMutableStateへと簡単に変換する関数も用意されている:
📚 Flowの場合:
-
collectAsStateWithLifeCycle関数
または - collectAsStateを使う。
.
📚 LiveDataの場合:
- observeAsState関数を使う。
.
.
.
.
.
.
.
.
.
.