Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Jetpack Compose の Side-effect を使いこなす / DroidKai...
Search
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
star_zero
September 15, 2023
Programming
5
6.7k
Jetpack Compose の Side-effect を使いこなす / DroidKaigi 2023
star_zero
September 15, 2023
Tweet
Share
More Decks by star_zero
See All by star_zero
今からはじめるAndroidアプリ開発 2024 / DevFest 2024
star_zero
0
1.5k
Android 14 新機能 / Android 14 Meetup Nagoya
star_zero
1
630
Android 14 と Predictive back gesture / Shibuya.apk #42
star_zero
0
440
Coroutines Test 入門 / Android Test Night #8
star_zero
2
1.2k
What's new in Jetpack / I/O Extended Japan 2022
star_zero
1
680
Kotlin 2021 Recap / DevFest 2021
star_zero
3
1.3k
Kotlin Symbol Processing (KSP) を使ったコード生成 / DroidKaigi 2021
star_zero
2
5.3k
What's new Android 12
star_zero
0
600
これからはじめるAndroid開発 / DevFest 2020
star_zero
4
750
Other Decks in Programming
See All in Programming
疑似コードによるプロンプト記述、どのくらい正確に実行される?
kokuyouwind
0
360
OSSとなったswift-buildで Xcodeのビルドを差し替えられるため 自分でXcodeを直せる時代になっている ダイアモンド問題編
yimajo
3
550
フルサイクルエンジニアリングをAI Agentで全自動化したい 〜構想と現在地〜
kamina_zzz
0
390
ZJIT: The Ruby 4 JIT Compiler / Ruby Release 30th Anniversary Party
k0kubun
1
390
公共交通オープンデータ × モバイルUX 複雑な運行情報を 『直感』に変換する技術
tinykitten
PRO
0
200
AIで開発はどれくらい加速したのか?AIエージェントによるコード生成を、現場の評価と研究開発の評価の両面からdeep diveしてみる
daisuketakeda
1
920
React 19でつくる「気持ちいいUI」- 楽観的UIのすすめ
himorishige
11
5.8k
組織で育むオブザーバビリティ
ryota_hnk
0
150
それ、本当に安全? ファイルアップロードで見落としがちなセキュリティリスクと対策
penpeen
7
2.4k
20260127_試行錯誤の結晶を1冊に。著者が解説 先輩データサイエンティストからの指南書 / author's_commentary_ds_instructions_guide
nash_efp
0
720
メルカリのリーダビリティチームが取り組む、AI時代のスケーラブルな品質文化
cloverrose
2
500
Graviton と Nitro と私
maroon1st
0
180
Featured
See All Featured
WENDY [Excerpt]
tessaabrams
9
36k
Navigating Team Friction
lara
192
16k
End of SEO as We Know It (SMX Advanced Version)
ipullrank
3
3.9k
Ecommerce SEO: The Keys for Success Now & Beyond - #SERPConf2024
aleyda
1
1.8k
Pawsitive SEO: Lessons from My Dog (and Many Mistakes) on Thriving as a Consultant in the Age of AI
davidcarrasco
0
58
VelocityConf: Rendering Performance Case Studies
addyosmani
333
24k
The B2B funnel & how to create a winning content strategy
katarinadahlin
PRO
0
260
How Software Deployment tools have changed in the past 20 years
geshan
0
31k
Building Better People: How to give real-time feedback that sticks.
wjessup
370
20k
Distributed Sagas: A Protocol for Coordinating Microservices
caitiem20
333
22k
The Director’s Chair: Orchestrating AI for Truly Effective Learning
tmiket
1
82
The Illustrated Children's Guide to Kubernetes
chrisshort
51
51k
Transcript
Jetpack Compose の Side-effect を使いこなす Kenji Abe - 2023/09/15
自己紹介 • Kenji Abe • Google Developers Expert for Android,
Kotlin • DeNA Co., Ltd. • X: @STAR_ZERO • Bluesky: @star-zero.com 2
Side-effect? (副作用) 3
Side effect (副作用) • 関数や操作などが結果を返すなどの主となる効果以外の効果 ◦ グローバル変数の変更 ◦ I/O操作 •
ほかのSide effectを起こす関数の呼び出し • 実行順によって結果が変わる可能性がある • デバッグが困難になる • テストが難しくなる 4
Composeにおける Side effect 5
副作用とは、 コンポーズ可能な関数の範囲外で 発生するアプリの状態の変化を 指します。 6 https://developer.android.com/jetpack/compose/side-effects?hl=ja
ComposeにおけるSide effect • Composable関数はSide effectがないようにすることが理想 • 予測できないRecomposition ◦ 実行順 ◦
スキップ ◦ 破棄 7
Side effectが必要な状況 8
Side effectが必要な状況 • スナックバーを表示する • 1 回限りのイベントをトリガーする • 特定の状態で別の画面に移動する •
などなど 9 https://developer.android.com/jetpack/compose/side-effects?hl=ja
Side effectが必要な状況 • スナックバーを表示する • 1 回限りのイベントをトリガーする • 特定の状態で別の画面に移動する •
などなど 10 https://developer.android.com/jetpack/compose/side-effects?hl=ja Side effectを安全に 実行する必要がある
Side effect APIs • LaunchedEffect • rememberCoroutineScope • DisposableEffect •
rememberUpdatedState • SideEffect • derivedStateOf • produceState • snapshotFlow 11 https://developer.android.com/jetpack/compose/side-effects?hl=ja
LaunchedEffect 12
LauchedEffect var showSnackbar by remember { mutableStateOf(false) } LaunchedEffect(showSnackbar) {
if (showSnackbar) { snackbarHostState.showSnackbar( message = "OK" ) showSnackbar = false } }
var showSnackbar by remember { mutableStateOf(false) } LaunchedEffect(showSnackbar) { if
(showSnackbar) { snackbarHostState.showSnackbar( message = "OK" ) showSnackbar = false } } LauchedEffect
var showSnackbar by remember { mutableStateOf(false) } LaunchedEffect(showSnackbar) { if
(showSnackbar) { snackbarHostState.showSnackbar( message = "OK" ) showSnackbar = false } } LauchedEffect 後述
var showSnackbar by remember { mutableStateOf(false) } LaunchedEffect(showSnackbar) { if
(showSnackbar) { snackbarHostState.showSnackbar( message = "OK" ) showSnackbar = false } } LauchedEffect Coroutines
var showSnackbar by remember { mutableStateOf(false) } LaunchedEffect(showSnackbar) { if
(showSnackbar) { snackbarHostState.showSnackbar( message = "OK" ) showSnackbar = false } } LauchedEffect Snackbarを表示
var showSnackbar by remember { mutableStateOf(false) } LaunchedEffect(showSnackbar) { if
(showSnackbar) { snackbarHostState.showSnackbar( message = "OK" ) showSnackbar = false } } LauchedEffect フラグを戻して、次回も表示できるように
LaunchedEffectの 引数について 19
var showSnackbar by remember { mutableStateOf(false) } LaunchedEffect(showSnackbar) { if
(showSnackbar) { snackbarHostState.showSnackbar( message = "OK" ) showSnackbar = false } } LauchedEffect Key: 値が変わったときに起動される
var showSnackbar by remember { mutableStateOf(false) } LaunchedEffect(showSnackbar) { if
(showSnackbar) { snackbarHostState.showSnackbar( message = "OK" ) showSnackbar = false } } LauchedEffect false 1
var showSnackbar by remember { mutableStateOf(false) } LaunchedEffect(showSnackbar) { if
(showSnackbar) { snackbarHostState.showSnackbar( message = "OK" ) showSnackbar = false } } LauchedEffect false 1 初回は必ず起動する 2
var showSnackbar by remember { mutableStateOf(false) } LaunchedEffect(showSnackbar) { if
(showSnackbar) { snackbarHostState.showSnackbar( message = "OK" ) showSnackbar = false } } LauchedEffect Recomposition
var showSnackbar by remember { mutableStateOf(false) } LaunchedEffect(showSnackbar) { if
(showSnackbar) { snackbarHostState.showSnackbar( message = "OK" ) showSnackbar = false } } LauchedEffect false のまま 3
var showSnackbar by remember { mutableStateOf(false) } LaunchedEffect(showSnackbar) { if
(showSnackbar) { snackbarHostState.showSnackbar( message = "OK" ) showSnackbar = false } } LauchedEffect false のまま 3 Key が変更されてないので 実行されない 4
var showSnackbar by remember { mutableStateOf(false) } LaunchedEffect(showSnackbar) { if
(showSnackbar) { snackbarHostState.showSnackbar( message = "OK" ) showSnackbar = false } } LauchedEffect showSnackbar = true
var showSnackbar by remember { mutableStateOf(false) } LaunchedEffect(showSnackbar) { if
(showSnackbar) { snackbarHostState.showSnackbar( message = "OK" ) showSnackbar = false } } LauchedEffect false → true 5
var showSnackbar by remember { mutableStateOf(false) } LaunchedEffect(showSnackbar) { if
(showSnackbar) { snackbarHostState.showSnackbar( message = "OK" ) showSnackbar = false } } LauchedEffect false → true 5 Key が変更されたので 実行される 6
いろいろなKey 29
LaunchedEffect(Unit) { // ... } LauchedEffect ずっと同じ値 最初の1回だけ実行
LaunchedEffect(key1, key2, key3) { // ... } LauchedEffect 複数のKey どれかが変わったら実行する
LaunchedEffect のCoroutines 32
LaunchedEffectのCoroutines • キャンセルするタイミング ◦ keyが変わってLauchedEffectが再起動するとき ◦ CompositionからLeaveするとき 33 https://developer.android.com/jetpack/compose/lifecycle
LaunchedEffect(key) { delay(5000) Timber.d("OK") } LauchedEffect
LaunchedEffect(key) { delay(5000) Timber.d("OK") } LauchedEffect 5秒経過前にkeyが変わると ログが出力されずに最初から数え直し
LaunchedEffect(key) { delay(5000) Timber.d("OK") } LauchedEffect 5秒経過前にCompositionからLeaveすると ログが出力されずに終了
rememberCoroutineScope 37
rememberCoroutineScope val scope = rememberCoroutineScope() Button( onClick = { scope.launch
{ snackbarHostState.showSnackbar("Hello, World") } } ) { Text(text = "Show Snackbar") }
val scope = rememberCoroutineScope() Button( onClick = { scope.launch {
snackbarHostState.showSnackbar("Hello, World") } } ) { Text(text = "Show Snackbar") } rememberCoroutineScope CoroutinesScope取得
val scope = rememberCoroutineScope() Button( onClick = { scope.launch {
snackbarHostState.showSnackbar("Hello, World") } } ) { Text(text = "Show Snackbar") } rememberCoroutineScope ScopeからCoroutines起動
val scope = rememberCoroutineScope() Button( onClick = { scope.launch {
snackbarHostState.showSnackbar("Hello, World") } } ) { Text(text = "Show Snackbar") } rememberCoroutineScope Composition から Leave するときにキャンセル
DisposableEffect 45
DisposableEffect val lifecycleOwner = LocalLifecycleOwner.current DisposableEffect(lifecycleOwner) { val observer =
LifecycleEventObserver { _, event -> if (event == Lifecycle.Event.ON_START) { // ... } } lifecycleOwner.lifecycle.addObserver(observer) onDispose { lifecycleOwner.lifecycle.removeObserver(observer) } }
val lifecycleOwner = LocalLifecycleOwner.current DisposableEffect(lifecycleOwner) { val observer = LifecycleEventObserver
{ _, event -> if (event == Lifecycle.Event.ON_START) { // ... } } lifecycleOwner.lifecycle.addObserver(observer) onDispose { lifecycleOwner.lifecycle.removeObserver(observer) } } DisposableEffect LifecycleOwner取得
val lifecycleOwner = LocalLifecycleOwner.current DisposableEffect(lifecycleOwner) { val observer = LifecycleEventObserver
{ _, event -> if (event == Lifecycle.Event.ON_START) { // ... } } lifecycleOwner.lifecycle.addObserver(observer) onDispose { lifecycleOwner.lifecycle.removeObserver(observer) } } DisposableEffect 引数は LauchedEffect と同じ感じ
val lifecycleOwner = LocalLifecycleOwner.current DisposableEffect(lifecycleOwner) { val observer = LifecycleEventObserver
{ _, event -> if (event == Lifecycle.Event.ON_START) { // ... } } lifecycleOwner.lifecycle.addObserver(observer) onDispose { lifecycleOwner.lifecycle.removeObserver(observer) } } DisposableEffect Coroutinesじゃない
val lifecycleOwner = LocalLifecycleOwner.current DisposableEffect(lifecycleOwner) { val observer = LifecycleEventObserver
{ _, event -> if (event == Lifecycle.Event.ON_START) { // ... } } lifecycleOwner.lifecycle.addObserver(observer) onDispose { lifecycleOwner.lifecycle.removeObserver(observer) } } DisposableEffect Lifecycle監視の処理
val lifecycleOwner = LocalLifecycleOwner.current DisposableEffect(lifecycleOwner) { val observer = LifecycleEventObserver
{ _, event -> if (event == Lifecycle.Event.ON_START) { // ... } } lifecycleOwner.lifecycle.addObserver(observer) onDispose { lifecycleOwner.lifecycle.removeObserver(observer) } } DisposableEffect クリーンアップの処理
DisposableEffect の処理の流れ 52
DisposableEffect var flag by remember { mutableStateOf(false) } DisposableEffect(flag) {
// ... onDispose { // ... } }
var flag by remember { mutableStateOf(false) } DisposableEffect(flag) { //
... onDispose { // ... } } DisposableEffect false 1
var flag by remember { mutableStateOf(false) } DisposableEffect(flag) { //
... onDispose { // ... } } DisposableEffect false 1 実行 2
var flag by remember { mutableStateOf(false) } DisposableEffect(flag) { //
... onDispose { // ... } } DisposableEffect false → true 3
var flag by remember { mutableStateOf(false) } DisposableEffect(flag) { //
... onDispose { // ... } } DisposableEffect 実行 4
var flag by remember { mutableStateOf(false) } DisposableEffect(flag) { //
... onDispose { // ... } } DisposableEffect 実行 5
59 LaunchedEffect DisposableEffect • クリーンアップできない • Coroutinesで起動 ◦ 若干のオーバーヘッド •
onDisposeによるクリーンアップ • Coroutinesではない
rememberUpdatedState 60
rememberUpdatedState を使わなかった場合 61
var count by remember { mutableIntStateOf(0) } Button(onClick = {
count++ }) { Text(text = "Increment: $count") } Content(count = count) rememberUpdatedState
var count by remember { mutableIntStateOf(0) } Button(onClick = {
count++ }) { Text(text = "Increment: $count") } Content(count = count) rememberUpdatedState 単純なインクリメント処理
var count by remember { mutableIntStateOf(0) } Button(onClick = {
count++ }) { Text(text = "Increment: $count") } Content(count = count) rememberUpdatedState 別のComposable関数へ渡す
@Composable private fun Content(count: Int) { LaunchedEffect(Unit) { delay(5000) Timber.d("Count
= $count") } } rememberUpdatedState
@Composable private fun Content(count: Int) { LaunchedEffect(Unit) { delay(5000) Timber.d("Count
= $count") } } rememberUpdatedState 0, 1, 2 … と増えていく
@Composable private fun Content(count: Int) { LaunchedEffect(Unit) { delay(5000) Timber.d("Count
= $count") } } rememberUpdatedState 最初の1回だけ実行
@Composable private fun Content(count: Int) { LaunchedEffect(Unit) { delay(5000) Timber.d("Count
= $count") } } rememberUpdatedState 5秒後にログを表示
@Composable private fun Content(count: Int) { LaunchedEffect(Unit) { delay(5000) Timber.d("Count
= $count") } } rememberUpdatedState ???
@Composable private fun Content(count: Int) { LaunchedEffect(Unit) { delay(5000) Timber.d("Count
= $count") } } rememberUpdatedState Count = 0
@Composable private fun Content(count: Int) { LaunchedEffect(Unit) { delay(5000) Timber.d("Count
= $count") } } rememberUpdatedState Count = 0 最初の引数がキャプチャされるので 0 のまま
値が変わったら LauchedEffect を再起動する 72
@Composable private fun Content(count: Int) { LaunchedEffect(count) { delay(5000) Timber.d("Count
= $count") } } rememberUpdatedState
@Composable private fun Content(count: Int) { LaunchedEffect(count) { delay(5000) Timber.d("Count
= $count") } } rememberUpdatedState 値が変わったら再起動
@Composable private fun Content(count: Int) { LaunchedEffect(count) { delay(5000) Timber.d("Count
= $count") } } rememberUpdatedState 値が変わるたびに最初から数え直し
rememberUpdatedState を使う 76
@Composable private fun Content(count: Int) { val currentCount by rememberUpdatedState(count)
LaunchedEffect(Unit) { delay(5000) Timber.d("Count = $currentCount") } } rememberUpdatedState
@Composable private fun Content(count: Int) { val currentCount by rememberUpdatedState(count)
LaunchedEffect(Unit) { delay(5000) Timber.d("Count = $currentCount") } } rememberUpdatedState 引数の値を rememberUpdatedState で Stateに変換
@Composable private fun Content(count: Int) { val currentCount by rememberUpdatedState(count)
LaunchedEffect(Unit) { delay(5000) Timber.d("Count = $currentCount") } } rememberUpdatedState 変換した変数を参照
val currentCount = rememberUpdatedState(count) val currentCount = remember { mutableIntStateOf(count)
} currentCount.value = count rememberUpdatedState
rememberUpdatedState が必要かどうかの判断 81
rememberUpdatedStateが必要か検討するポイント • Composeの外で変化する状態を参照している • LauchedEffect, DisposableEffect の Key ではない状態を参照 •
時間が経過して使用される状態への参照 • コールバック関数 82
SideEffect 83
@Composable fun rememberAnalytics(user: User): FirebaseAnalytics { val analytics = remember
{ Firebase.analytics } SideEffect { analytics.setUserProperty("user_type", user.type) } return analytics } SideEffect
@Composable fun rememberAnalytics(user: User): FirebaseAnalytics { val analytics = remember
{ Firebase.analytics } SideEffect { analytics.setUserProperty("user_type", user.type) } return analytics } SideEffect
@Composable fun rememberAnalytics(user: User): FirebaseAnalytics { val analytics = remember
{ Firebase.analytics } SideEffect { analytics.setUserProperty("user_type", user.type) } return analytics } SideEffect
@Composable fun rememberAnalytics(user: User): FirebaseAnalytics { val analytics = remember
{ Firebase.analytics } SideEffect { analytics.setUserProperty("user_type", user.type) } return analytics } SideEffect Userの状態が更新 → Recomposition 1
@Composable fun rememberAnalytics(user: User): FirebaseAnalytics { val analytics = remember
{ Firebase.analytics } SideEffect { analytics.setUserProperty("user_type", user.type) } return analytics } SideEffect Userの状態が更新 → Recomposition Recomposition → SideEffect実行 1 2
SideEffect のメリット 89
@Composable fun rememberAnalytics(user: User): FirebaseAnalytics { val analytics = remember
{ Firebase.analytics } SideEffect { analytics.setUserProperty("user_type", user.type) } analytics.setUserProperty("user_type", user.type) return analytics } SideEffect これと同じ??
@Composable fun rememberAnalytics(user: User): FirebaseAnalytics { val analytics = remember
{ Firebase.analytics } SideEffect { analytics.setUserProperty("user_type", user.type) } analytics.setUserProperty("user_type", user.type) error("Error") return analytics } SideEffect エラー発生
@Composable fun rememberAnalytics(user: User): FirebaseAnalytics { val analytics = remember
{ Firebase.analytics } SideEffect { analytics.setUserProperty("user_type", user.type) } analytics.setUserProperty("user_type", user.type) error("Error") return analytics } SideEffect 実行されない 実行される
SideEffect とログ 93
val listState = rememberLazyListState() LazyColumn(state = listState) { // ...
} Timber.d("index = ${listState.firstVisibleItemIndex}") SideEffect
val listState = rememberLazyListState() LazyColumn(state = listState) { // ...
} Timber.d("index = ${listState.firstVisibleItemIndex}") SideEffect スクロールのたびに状態が変わる
val listState = rememberLazyListState() LazyColumn(state = listState) { // ...
} Timber.d("index = ${listState.firstVisibleItemIndex}") SideEffect スクロールのたびに状態が変わる 状態の参照 = Recomposition対象
val listState = rememberLazyListState() LazyColumn(state = listState) { // ...
} Timber.d("index = ${listState.firstVisibleItemIndex}") SideEffect スクロールのたびに状態が変わる 状態の参照 = Recomposition対象 スクロールのたびに Recomposition
val listState = rememberLazyListState() LazyColumn(state = listState) { // ...
} Timber.d("index = ${listState.firstVisibleItemIndex}") SideEffect { Timber.d("index = ${listState.firstVisibleItemIndex}") } SideEffect これが要因でRecompositionが発生しなくなる
derivedStateOf 99
Box { val listState = rememberLazyListState() LazyColumn(state = listState) {
/* ... */ } val showButton = listState.firstVisibleItemIndex > 0 if (showButton) { Button() { Text(text = "ScrollTop") } } } derivedStateOf
Box { val listState = rememberLazyListState() LazyColumn(state = listState) {
/* ... */ } val showButton = listState.firstVisibleItemIndex > 0 if (showButton) { Button() { Text(text = "ScrollTop") } } } derivedStateOf スクロールしている場合はボタンを表示
Box { val listState = rememberLazyListState() LazyColumn(state = listState) {
/* ... */ } val showButton = listState.firstVisibleItemIndex > 0 if (showButton) { Button() { Text(text = "ScrollTop") } } } derivedStateOf スクロールのたびに Recomposition スクロールのたびに状態が変わる
Box { val listState = rememberLazyListState() LazyColumn(state = listState) {
/* ... */ } val showButton = listState.firstVisibleItemIndex > 0 if (showButton) { Button() { Text(text = "ScrollTop") } } } derivedStateOf スクロールのたびに Recomposition スクロールのたびに状態が変わる ここが変わったときだけ Recompositionしてほしい
Box { // ... val showButton = listState.firstVisibleItemIndex > 0
val showButton by remember { derivedStateOf { listState.firstVisibleItemIndex > 0 } } // ... } derivedStateOf derivedStateOf で結果が変わった時に Recompositionするようにする
derivedStateOf を 使わないほうが良い場合 105
@Composable fun Content(friends: List<User>) { val friendsCount by remember {
derivedStateOf { friends.size } } } derivedStateOf
@Composable fun Content(friends: List<User>) { val friendsCount by remember {
derivedStateOf { friends.size } } } derivedStateOf サイズが変更されたら変数の値も更新
@Composable fun Content(friends: List<User>) { val friendsCount by remember {
derivedStateOf { friends.size } } val friendsCount = friends.size } derivedStateOf
produceState 109
val count by produceState(0) { (1..10).forEach { delay(1000) value =
it } } Text(text = "Count = $count") produceState
val count by produceState(0) { (1..10).forEach { delay(1000) value =
it } } Text(text = "Count = $count") produceState 初期値 0 初期値 0
val count by produceState(0) { (1..10).forEach { delay(1000) value =
it } } Text(text = "Count = $count") produceState 1から10までを1秒ごとにループ
val count by produceState(0) { (1..10).forEach { delay(1000) value =
it } } Text(text = "Count = $count") produceState value に値を設定 1
val count by produceState(0) { (1..10).forEach { delay(1000) value =
it } } Text(text = "Count = $count") produceState value に値を設定 count に設定される 1 2
val count by produceState(0) { (1..10).forEach { delay(1000) value =
it } } Text(text = "Count = $count") produceState value に値を設定 count に設定される 表示 1 2 3
クリーンアップが必要な時 116
val count by produceState(0) { // ... awaitDispose { //
... } } produceState クリーンアップ処理
snapshotFlow 118
snapshotFlow var count by remember { mutableIntStateOf(0) } LaunchedEffect(Unit) {
snapshotFlow { count }.map { "Count = $it" }.collect { Timber.d(it) } }
var count by remember { mutableIntStateOf(0) } LaunchedEffect(Unit) { snapshotFlow
{ count }.map { "Count = $it" }.collect { Timber.d(it) } } snapshotFlow 更新されていくカウント
var count by remember { mutableIntStateOf(0) } LaunchedEffect(Unit) { snapshotFlow
{ count }.map { "Count = $it" }.collect { Timber.d(it) } } snapshotFlow
var count by remember { mutableIntStateOf(0) } LaunchedEffect(Unit) { snapshotFlow
{ count }.map { "Count = $it" }.collect { Timber.d(it) } } snapshotFlow State を Flow に変換
var count by remember { mutableIntStateOf(0) } LaunchedEffect(Unit) { snapshotFlow
{ count }.map { "Count = $it" }.collect { Timber.d(it) } } snapshotFlow Flow の処理
var firstName by remember { mutableStateOf("") } var lastName by
remember { mutableStateOf("") } LaunchedEffect(Unit) { snapshotFlow { "$firstName $lastName" }.collect { Timber.d(it) } } snapshotFlow 2つのStateを組み合わせた結果
まとめ 125
まとめ • LaunchedEffect ◦ 状態が変わった時に何かしたい時 ◦ Coroutines • rememberCoroutineScope ◦
UIイベントからCoroutinesの処理をしたい時 • DisposableEffect ◦ 何かクリーンアップが必要な時 • rememberUpdatedState ◦ 変化する状態を参照している時 126
まとめ • SideEffect ◦ Compositionが成功した時に何かしたい時 ◦ ログ • derivedStateOf ◦
状態から別の値を派生させたい時 • produceState ◦ Compose外の状態をComposeのStateに変換したい時 • snapshotFlow ◦ ComposeのStateの更新をCompose外でFlowで受け取りたい時 127
ありがとうございました 128