Slide 1

Slide 1 text

Jetpack Compose の Side-effect を使いこなす Kenji Abe - 2023/09/15

Slide 2

Slide 2 text

自己紹介 ● Kenji Abe ● Google Developers Expert for Android, Kotlin ● DeNA Co., Ltd. ● X: @STAR_ZERO ● Bluesky: @star-zero.com 2

Slide 3

Slide 3 text

Side-effect? (副作用) 3

Slide 4

Slide 4 text

Side effect (副作用) ● 関数や操作などが結果を返すなどの主となる効果以外の効果 ○ グローバル変数の変更 ○ I/O操作 ● ほかのSide effectを起こす関数の呼び出し ● 実行順によって結果が変わる可能性がある ● デバッグが困難になる ● テストが難しくなる 4

Slide 5

Slide 5 text

Composeにおける Side effect 5

Slide 6

Slide 6 text

副作用とは、 コンポーズ可能な関数の範囲外で 発生するアプリの状態の変化を 指します。 6 https://developer.android.com/jetpack/compose/side-effects?hl=ja

Slide 7

Slide 7 text

ComposeにおけるSide effect ● Composable関数はSide effectがないようにすることが理想 ● 予測できないRecomposition ○ 実行順 ○ スキップ ○ 破棄 7

Slide 8

Slide 8 text

Side effectが必要な状況 8

Slide 9

Slide 9 text

Side effectが必要な状況 ● スナックバーを表示する ● 1 回限りのイベントをトリガーする ● 特定の状態で別の画面に移動する ● などなど 9 https://developer.android.com/jetpack/compose/side-effects?hl=ja

Slide 10

Slide 10 text

Side effectが必要な状況 ● スナックバーを表示する ● 1 回限りのイベントをトリガーする ● 特定の状態で別の画面に移動する ● などなど 10 https://developer.android.com/jetpack/compose/side-effects?hl=ja Side effectを安全に 実行する必要がある

Slide 11

Slide 11 text

Side effect APIs ● LaunchedEffect ● rememberCoroutineScope ● DisposableEffect ● rememberUpdatedState ● SideEffect ● derivedStateOf ● produceState ● snapshotFlow 11 https://developer.android.com/jetpack/compose/side-effects?hl=ja

Slide 12

Slide 12 text

LaunchedEffect 12

Slide 13

Slide 13 text

LauchedEffect var showSnackbar by remember { mutableStateOf(false) } LaunchedEffect(showSnackbar) { if (showSnackbar) { snackbarHostState.showSnackbar( message = "OK" ) showSnackbar = false } }

Slide 14

Slide 14 text

var showSnackbar by remember { mutableStateOf(false) } LaunchedEffect(showSnackbar) { if (showSnackbar) { snackbarHostState.showSnackbar( message = "OK" ) showSnackbar = false } } LauchedEffect

Slide 15

Slide 15 text

var showSnackbar by remember { mutableStateOf(false) } LaunchedEffect(showSnackbar) { if (showSnackbar) { snackbarHostState.showSnackbar( message = "OK" ) showSnackbar = false } } LauchedEffect 後述

Slide 16

Slide 16 text

var showSnackbar by remember { mutableStateOf(false) } LaunchedEffect(showSnackbar) { if (showSnackbar) { snackbarHostState.showSnackbar( message = "OK" ) showSnackbar = false } } LauchedEffect Coroutines

Slide 17

Slide 17 text

var showSnackbar by remember { mutableStateOf(false) } LaunchedEffect(showSnackbar) { if (showSnackbar) { snackbarHostState.showSnackbar( message = "OK" ) showSnackbar = false } } LauchedEffect Snackbarを表示

Slide 18

Slide 18 text

var showSnackbar by remember { mutableStateOf(false) } LaunchedEffect(showSnackbar) { if (showSnackbar) { snackbarHostState.showSnackbar( message = "OK" ) showSnackbar = false } } LauchedEffect フラグを戻して、次回も表示できるように

Slide 19

Slide 19 text

LaunchedEffectの 引数について 19

Slide 20

Slide 20 text

var showSnackbar by remember { mutableStateOf(false) } LaunchedEffect(showSnackbar) { if (showSnackbar) { snackbarHostState.showSnackbar( message = "OK" ) showSnackbar = false } } LauchedEffect Key: 値が変わったときに起動される

Slide 21

Slide 21 text

var showSnackbar by remember { mutableStateOf(false) } LaunchedEffect(showSnackbar) { if (showSnackbar) { snackbarHostState.showSnackbar( message = "OK" ) showSnackbar = false } } LauchedEffect false 1

Slide 22

Slide 22 text

var showSnackbar by remember { mutableStateOf(false) } LaunchedEffect(showSnackbar) { if (showSnackbar) { snackbarHostState.showSnackbar( message = "OK" ) showSnackbar = false } } LauchedEffect false 1 初回は必ず起動する 2

Slide 23

Slide 23 text

var showSnackbar by remember { mutableStateOf(false) } LaunchedEffect(showSnackbar) { if (showSnackbar) { snackbarHostState.showSnackbar( message = "OK" ) showSnackbar = false } } LauchedEffect Recomposition

Slide 24

Slide 24 text

var showSnackbar by remember { mutableStateOf(false) } LaunchedEffect(showSnackbar) { if (showSnackbar) { snackbarHostState.showSnackbar( message = "OK" ) showSnackbar = false } } LauchedEffect false のまま 3

Slide 25

Slide 25 text

var showSnackbar by remember { mutableStateOf(false) } LaunchedEffect(showSnackbar) { if (showSnackbar) { snackbarHostState.showSnackbar( message = "OK" ) showSnackbar = false } } LauchedEffect false のまま 3 Key が変更されてないので 実行されない 4

Slide 26

Slide 26 text

var showSnackbar by remember { mutableStateOf(false) } LaunchedEffect(showSnackbar) { if (showSnackbar) { snackbarHostState.showSnackbar( message = "OK" ) showSnackbar = false } } LauchedEffect showSnackbar = true

Slide 27

Slide 27 text

var showSnackbar by remember { mutableStateOf(false) } LaunchedEffect(showSnackbar) { if (showSnackbar) { snackbarHostState.showSnackbar( message = "OK" ) showSnackbar = false } } LauchedEffect false → true 5

Slide 28

Slide 28 text

var showSnackbar by remember { mutableStateOf(false) } LaunchedEffect(showSnackbar) { if (showSnackbar) { snackbarHostState.showSnackbar( message = "OK" ) showSnackbar = false } } LauchedEffect false → true 5 Key が変更されたので 実行される 6

Slide 29

Slide 29 text

いろいろなKey 29

Slide 30

Slide 30 text

LaunchedEffect(Unit) { // ... } LauchedEffect ずっと同じ値 最初の1回だけ実行

Slide 31

Slide 31 text

LaunchedEffect(key1, key2, key3) { // ... } LauchedEffect 複数のKey どれかが変わったら実行する

Slide 32

Slide 32 text

LaunchedEffect のCoroutines 32

Slide 33

Slide 33 text

LaunchedEffectのCoroutines ● キャンセルするタイミング ○ keyが変わってLauchedEffectが再起動するとき ○ CompositionからLeaveするとき 33 https://developer.android.com/jetpack/compose/lifecycle

Slide 34

Slide 34 text

LaunchedEffect(key) { delay(5000) Timber.d("OK") } LauchedEffect

Slide 35

Slide 35 text

LaunchedEffect(key) { delay(5000) Timber.d("OK") } LauchedEffect 5秒経過前にkeyが変わると ログが出力されずに最初から数え直し

Slide 36

Slide 36 text

LaunchedEffect(key) { delay(5000) Timber.d("OK") } LauchedEffect 5秒経過前にCompositionからLeaveすると ログが出力されずに終了

Slide 37

Slide 37 text

rememberCoroutineScope 37

Slide 38

Slide 38 text

rememberCoroutineScope val scope = rememberCoroutineScope() Button( onClick = { scope.launch { snackbarHostState.showSnackbar("Hello, World") } } ) { Text(text = "Show Snackbar") }

Slide 39

Slide 39 text

val scope = rememberCoroutineScope() Button( onClick = { scope.launch { snackbarHostState.showSnackbar("Hello, World") } } ) { Text(text = "Show Snackbar") } rememberCoroutineScope CoroutinesScope取得

Slide 40

Slide 40 text

val scope = rememberCoroutineScope() Button( onClick = { scope.launch { snackbarHostState.showSnackbar("Hello, World") } } ) { Text(text = "Show Snackbar") } rememberCoroutineScope ScopeからCoroutines起動

Slide 41

Slide 41 text

val scope = rememberCoroutineScope() Button( onClick = { scope.launch { snackbarHostState.showSnackbar("Hello, World") } } ) { Text(text = "Show Snackbar") } rememberCoroutineScope Composition から Leave するときにキャンセル

Slide 42

Slide 42 text

DisposableEffect 45

Slide 43

Slide 43 text

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) } }

Slide 44

Slide 44 text

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取得

Slide 45

Slide 45 text

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 と同じ感じ

Slide 46

Slide 46 text

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じゃない

Slide 47

Slide 47 text

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監視の処理

Slide 48

Slide 48 text

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 クリーンアップの処理

Slide 49

Slide 49 text

DisposableEffect の処理の流れ 52

Slide 50

Slide 50 text

DisposableEffect var flag by remember { mutableStateOf(false) } DisposableEffect(flag) { // ... onDispose { // ... } }

Slide 51

Slide 51 text

var flag by remember { mutableStateOf(false) } DisposableEffect(flag) { // ... onDispose { // ... } } DisposableEffect false 1

Slide 52

Slide 52 text

var flag by remember { mutableStateOf(false) } DisposableEffect(flag) { // ... onDispose { // ... } } DisposableEffect false 1 実行 2

Slide 53

Slide 53 text

var flag by remember { mutableStateOf(false) } DisposableEffect(flag) { // ... onDispose { // ... } } DisposableEffect false → true 3

Slide 54

Slide 54 text

var flag by remember { mutableStateOf(false) } DisposableEffect(flag) { // ... onDispose { // ... } } DisposableEffect 実行 4

Slide 55

Slide 55 text

var flag by remember { mutableStateOf(false) } DisposableEffect(flag) { // ... onDispose { // ... } } DisposableEffect 実行 5

Slide 56

Slide 56 text

59 LaunchedEffect DisposableEffect ● クリーンアップできない ● Coroutinesで起動 ○ 若干のオーバーヘッド ● onDisposeによるクリーンアップ ● Coroutinesではない

Slide 57

Slide 57 text

rememberUpdatedState 60

Slide 58

Slide 58 text

rememberUpdatedState を使わなかった場合 61

Slide 59

Slide 59 text

var count by remember { mutableIntStateOf(0) } Button(onClick = { count++ }) { Text(text = "Increment: $count") } Content(count = count) rememberUpdatedState

Slide 60

Slide 60 text

var count by remember { mutableIntStateOf(0) } Button(onClick = { count++ }) { Text(text = "Increment: $count") } Content(count = count) rememberUpdatedState 単純なインクリメント処理

Slide 61

Slide 61 text

var count by remember { mutableIntStateOf(0) } Button(onClick = { count++ }) { Text(text = "Increment: $count") } Content(count = count) rememberUpdatedState 別のComposable関数へ渡す

Slide 62

Slide 62 text

@Composable private fun Content(count: Int) { LaunchedEffect(Unit) { delay(5000) Timber.d("Count = $count") } } rememberUpdatedState

Slide 63

Slide 63 text

@Composable private fun Content(count: Int) { LaunchedEffect(Unit) { delay(5000) Timber.d("Count = $count") } } rememberUpdatedState 0, 1, 2 … と増えていく

Slide 64

Slide 64 text

@Composable private fun Content(count: Int) { LaunchedEffect(Unit) { delay(5000) Timber.d("Count = $count") } } rememberUpdatedState 最初の1回だけ実行

Slide 65

Slide 65 text

@Composable private fun Content(count: Int) { LaunchedEffect(Unit) { delay(5000) Timber.d("Count = $count") } } rememberUpdatedState 5秒後にログを表示

Slide 66

Slide 66 text

@Composable private fun Content(count: Int) { LaunchedEffect(Unit) { delay(5000) Timber.d("Count = $count") } } rememberUpdatedState ???

Slide 67

Slide 67 text

@Composable private fun Content(count: Int) { LaunchedEffect(Unit) { delay(5000) Timber.d("Count = $count") } } rememberUpdatedState Count = 0

Slide 68

Slide 68 text

@Composable private fun Content(count: Int) { LaunchedEffect(Unit) { delay(5000) Timber.d("Count = $count") } } rememberUpdatedState Count = 0 最初の引数がキャプチャされるので 0 のまま

Slide 69

Slide 69 text

値が変わったら LauchedEffect を再起動する 72

Slide 70

Slide 70 text

@Composable private fun Content(count: Int) { LaunchedEffect(count) { delay(5000) Timber.d("Count = $count") } } rememberUpdatedState

Slide 71

Slide 71 text

@Composable private fun Content(count: Int) { LaunchedEffect(count) { delay(5000) Timber.d("Count = $count") } } rememberUpdatedState 値が変わったら再起動

Slide 72

Slide 72 text

@Composable private fun Content(count: Int) { LaunchedEffect(count) { delay(5000) Timber.d("Count = $count") } } rememberUpdatedState 値が変わるたびに最初から数え直し

Slide 73

Slide 73 text

rememberUpdatedState を使う 76

Slide 74

Slide 74 text

@Composable private fun Content(count: Int) { val currentCount by rememberUpdatedState(count) LaunchedEffect(Unit) { delay(5000) Timber.d("Count = $currentCount") } } rememberUpdatedState

Slide 75

Slide 75 text

@Composable private fun Content(count: Int) { val currentCount by rememberUpdatedState(count) LaunchedEffect(Unit) { delay(5000) Timber.d("Count = $currentCount") } } rememberUpdatedState 引数の値を rememberUpdatedState で Stateに変換

Slide 76

Slide 76 text

@Composable private fun Content(count: Int) { val currentCount by rememberUpdatedState(count) LaunchedEffect(Unit) { delay(5000) Timber.d("Count = $currentCount") } } rememberUpdatedState 変換した変数を参照

Slide 77

Slide 77 text

val currentCount = rememberUpdatedState(count) val currentCount = remember { mutableIntStateOf(count) } currentCount.value = count rememberUpdatedState

Slide 78

Slide 78 text

rememberUpdatedState が必要かどうかの判断 81

Slide 79

Slide 79 text

rememberUpdatedStateが必要か検討するポイント ● Composeの外で変化する状態を参照している ● LauchedEffect, DisposableEffect の Key ではない状態を参照 ● 時間が経過して使用される状態への参照 ● コールバック関数 82

Slide 80

Slide 80 text

SideEffect 83

Slide 81

Slide 81 text

@Composable fun rememberAnalytics(user: User): FirebaseAnalytics { val analytics = remember { Firebase.analytics } SideEffect { analytics.setUserProperty("user_type", user.type) } return analytics } SideEffect

Slide 82

Slide 82 text

@Composable fun rememberAnalytics(user: User): FirebaseAnalytics { val analytics = remember { Firebase.analytics } SideEffect { analytics.setUserProperty("user_type", user.type) } return analytics } SideEffect

Slide 83

Slide 83 text

@Composable fun rememberAnalytics(user: User): FirebaseAnalytics { val analytics = remember { Firebase.analytics } SideEffect { analytics.setUserProperty("user_type", user.type) } return analytics } SideEffect

Slide 84

Slide 84 text

@Composable fun rememberAnalytics(user: User): FirebaseAnalytics { val analytics = remember { Firebase.analytics } SideEffect { analytics.setUserProperty("user_type", user.type) } return analytics } SideEffect Userの状態が更新 → Recomposition 1

Slide 85

Slide 85 text

@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

Slide 86

Slide 86 text

SideEffect のメリット 89

Slide 87

Slide 87 text

@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 これと同じ??

Slide 88

Slide 88 text

@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 エラー発生

Slide 89

Slide 89 text

@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 実行されない 実行される

Slide 90

Slide 90 text

SideEffect とログ 93

Slide 91

Slide 91 text

val listState = rememberLazyListState() LazyColumn(state = listState) { // ... } Timber.d("index = ${listState.firstVisibleItemIndex}") SideEffect

Slide 92

Slide 92 text

val listState = rememberLazyListState() LazyColumn(state = listState) { // ... } Timber.d("index = ${listState.firstVisibleItemIndex}") SideEffect スクロールのたびに状態が変わる

Slide 93

Slide 93 text

val listState = rememberLazyListState() LazyColumn(state = listState) { // ... } Timber.d("index = ${listState.firstVisibleItemIndex}") SideEffect スクロールのたびに状態が変わる 状態の参照 = Recomposition対象

Slide 94

Slide 94 text

val listState = rememberLazyListState() LazyColumn(state = listState) { // ... } Timber.d("index = ${listState.firstVisibleItemIndex}") SideEffect スクロールのたびに状態が変わる 状態の参照 = Recomposition対象 スクロールのたびに Recomposition

Slide 95

Slide 95 text

val listState = rememberLazyListState() LazyColumn(state = listState) { // ... } Timber.d("index = ${listState.firstVisibleItemIndex}") SideEffect { Timber.d("index = ${listState.firstVisibleItemIndex}") } SideEffect これが要因でRecompositionが発生しなくなる

Slide 96

Slide 96 text

derivedStateOf 99

Slide 97

Slide 97 text

Box { val listState = rememberLazyListState() LazyColumn(state = listState) { /* ... */ } val showButton = listState.firstVisibleItemIndex > 0 if (showButton) { Button() { Text(text = "ScrollTop") } } } derivedStateOf

Slide 98

Slide 98 text

Box { val listState = rememberLazyListState() LazyColumn(state = listState) { /* ... */ } val showButton = listState.firstVisibleItemIndex > 0 if (showButton) { Button() { Text(text = "ScrollTop") } } } derivedStateOf スクロールしている場合はボタンを表示

Slide 99

Slide 99 text

Box { val listState = rememberLazyListState() LazyColumn(state = listState) { /* ... */ } val showButton = listState.firstVisibleItemIndex > 0 if (showButton) { Button() { Text(text = "ScrollTop") } } } derivedStateOf スクロールのたびに Recomposition スクロールのたびに状態が変わる

Slide 100

Slide 100 text

Box { val listState = rememberLazyListState() LazyColumn(state = listState) { /* ... */ } val showButton = listState.firstVisibleItemIndex > 0 if (showButton) { Button() { Text(text = "ScrollTop") } } } derivedStateOf スクロールのたびに Recomposition スクロールのたびに状態が変わる ここが変わったときだけ Recompositionしてほしい

Slide 101

Slide 101 text

Box { // ... val showButton = listState.firstVisibleItemIndex > 0 val showButton by remember { derivedStateOf { listState.firstVisibleItemIndex > 0 } } // ... } derivedStateOf derivedStateOf で結果が変わった時に Recompositionするようにする

Slide 102

Slide 102 text

derivedStateOf を 使わないほうが良い場合 105

Slide 103

Slide 103 text

@Composable fun Content(friends: List) { val friendsCount by remember { derivedStateOf { friends.size } } } derivedStateOf

Slide 104

Slide 104 text

@Composable fun Content(friends: List) { val friendsCount by remember { derivedStateOf { friends.size } } } derivedStateOf サイズが変更されたら変数の値も更新

Slide 105

Slide 105 text

@Composable fun Content(friends: List) { val friendsCount by remember { derivedStateOf { friends.size } } val friendsCount = friends.size } derivedStateOf

Slide 106

Slide 106 text

produceState 109

Slide 107

Slide 107 text

val count by produceState(0) { (1..10).forEach { delay(1000) value = it } } Text(text = "Count = $count") produceState

Slide 108

Slide 108 text

val count by produceState(0) { (1..10).forEach { delay(1000) value = it } } Text(text = "Count = $count") produceState 初期値 0 初期値 0

Slide 109

Slide 109 text

val count by produceState(0) { (1..10).forEach { delay(1000) value = it } } Text(text = "Count = $count") produceState 1から10までを1秒ごとにループ

Slide 110

Slide 110 text

val count by produceState(0) { (1..10).forEach { delay(1000) value = it } } Text(text = "Count = $count") produceState  value に値を設定 1

Slide 111

Slide 111 text

val count by produceState(0) { (1..10).forEach { delay(1000) value = it } } Text(text = "Count = $count") produceState  value に値を設定 count に設定される 1 2

Slide 112

Slide 112 text

val count by produceState(0) { (1..10).forEach { delay(1000) value = it } } Text(text = "Count = $count") produceState  value に値を設定 count に設定される 表示 1 2 3

Slide 113

Slide 113 text

クリーンアップが必要な時 116

Slide 114

Slide 114 text

val count by produceState(0) { // ... awaitDispose { // ... } } produceState  クリーンアップ処理

Slide 115

Slide 115 text

snapshotFlow 118

Slide 116

Slide 116 text

snapshotFlow var count by remember { mutableIntStateOf(0) } LaunchedEffect(Unit) { snapshotFlow { count }.map { "Count = $it" }.collect { Timber.d(it) } }

Slide 117

Slide 117 text

var count by remember { mutableIntStateOf(0) } LaunchedEffect(Unit) { snapshotFlow { count }.map { "Count = $it" }.collect { Timber.d(it) } } snapshotFlow  更新されていくカウント

Slide 118

Slide 118 text

var count by remember { mutableIntStateOf(0) } LaunchedEffect(Unit) { snapshotFlow { count }.map { "Count = $it" }.collect { Timber.d(it) } } snapshotFlow

Slide 119

Slide 119 text

var count by remember { mutableIntStateOf(0) } LaunchedEffect(Unit) { snapshotFlow { count }.map { "Count = $it" }.collect { Timber.d(it) } } snapshotFlow State を Flow に変換

Slide 120

Slide 120 text

var count by remember { mutableIntStateOf(0) } LaunchedEffect(Unit) { snapshotFlow { count }.map { "Count = $it" }.collect { Timber.d(it) } } snapshotFlow Flow の処理

Slide 121

Slide 121 text

var firstName by remember { mutableStateOf("") } var lastName by remember { mutableStateOf("") } LaunchedEffect(Unit) { snapshotFlow { "$firstName $lastName" }.collect { Timber.d(it) } } snapshotFlow 2つのStateを組み合わせた結果

Slide 122

Slide 122 text

まとめ 125

Slide 123

Slide 123 text

まとめ ● LaunchedEffect ○ 状態が変わった時に何かしたい時 ○ Coroutines ● rememberCoroutineScope ○ UIイベントからCoroutinesの処理をしたい時 ● DisposableEffect ○ 何かクリーンアップが必要な時 ● rememberUpdatedState ○ 変化する状態を参照している時 126

Slide 124

Slide 124 text

まとめ ● SideEffect ○ Compositionが成功した時に何かしたい時 ○ ログ ● derivedStateOf ○ 状態から別の値を派生させたい時 ● produceState ○ Compose外の状態をComposeのStateに変換したい時 ● snapshotFlow ○ ComposeのStateの更新をCompose外でFlowで受け取りたい時 127

Slide 125

Slide 125 text

ありがとうございました 128