JetpackComposeのEffect系関数一覧

# はじめに
Google公式ドキュメントで説明されているComposeのEffect系関数について、端的にまとめました。

https://developer.android.com/jetpack/compose/side-effects

## Effect系関数の役割・目的とは?
Composable関数の実行したときの副作用(Composable関数を実行したことによる、Composable関数外への影響)を定義するための関数。
(Google公式ドキュメント上では「Side-Effect APIs」と表現されている)

# LaunchedEffect
「非同期で実行される副作用」を定義する関数。

LaunchedEffect関数にkeyを設定することで、Recomposeに伴うCoroutineの再起動を抑止することができる。

以下の処理の場合、remember関数でsnackbarHostStateが生成されているため、snackbarHostStateはRecomposeを跨いで生存する。つまり、このsnackbarHostStateをkeyとしているLaunchedEffect関数は、Recomposeを跨いで非同期処理を続行する。
### LaunchedEffect単体
```kotlin:
@Composable
private fun VideoScreen(
    state: PlaybackState,
    snackbarHostState: SnackbarHostState = remember { SnackbarHostState() }
) {
    if (state is PlaybackState.ERROR) {
        LaunchedEffect(key1 = snackbarHostState) {
            // showSnackbarはsuspend関数
            snackbarHostState.showSnackbar(
                message = "再生に失敗しました",
                actionLabel = "Retry message"
            )
        }
    }
    
    /* 以下、UI構築処理 */
}
```
### LaunchedEffectとrememberUpdatedStateの組み合わせ
Recomposeに伴うLaunchedEffectの再起動は避けたいが、「LaunchedEffectの中で参照しているオブジェクト」は常に最新の値に保ちたいときは、rememberUpdatedState()を利用する。

```kotlin:
@Composable
fun LandingScreen(onTimeout: () -> Unit) {

    // Recomposeの度に最新のラムダ式が代入される
    val currentOnTimeout by rememberUpdatedState(onTimeout)

    // key1は固定値のため、Recompseで再起動することはない。
    LaunchedEffect(key1 = true) {
        delay(splashWaitTimeMillis)
        // recomposeで得られた最新のonTimeout関数を実行する
        currentOnTimeout()
    }

    /* 以下その他処理 */
}
```
#### rememberUpdatedStateを利用しなくても、引数として定義されている「onTimeout」をそのままLaunchedEffect内で実行すればよいのでは？  
No。その場合、LandingScreenのRecomposeが何度実行されても、初回Composeで渡したonTimeoutが実行されてしまう。
# DisposableEffect
「同期的に実行される副作用」を定義する関数。
DisposbleEffectでは、onDispose関数を用いることでComposition終了(leaves the Composition)時の挙動も定義できる。

また、LaunchedEffectと同様にkeyを設定することで、Recomposeに伴うDisposableEffectの再起動を抑止することができる。

### DisposableEffectとrememberUpdatedStateの組み合わせ
DisposableEffectの再起動は避けたいが、「DisposableEffectの中で参照しているオブジェクト (今回の場合は、onStartとonStop関数オブジェクト)」を常に最新の値に保ちたいときは、rememberUpdatedState()を利用する。

```kotlin:
@Composable
fun Screen(
    lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
    onStart: () -> Unit, 
    onStop: () -> Unit
) {
    val currentOnStart by rememberUpdatedState(onStart)
    val currentOnStop by rememberUpdatedState(onStop)

    DisposableEffect(key1 = lifecycleOwner) {
        val observer = LifecycleEventObserver { _, event ->
            if (event == Lifecycle.Event.ON_START) {
                currentOnStart()
            } else if (event == Lifecycle.Event.ON_STOP) {
                currentOnStop()
            }
        }

        lifecycleOwner.lifecycle.addObserver(observer)

        onDispose {
            lifecycleOwner.lifecycle.removeObserver(observer)
        }
    }

    /* 以下その他処理 */
}
```


#### rememberUpdatedStateを使わずに、onStartとonStopをそのままobserverの中で実行しても同じ結果になるのでは？
No。その場合、onStartもしくはonStopが更新されてHomeScreenがRecomposeしても、「observerの引数に与えたラムダ式」は永遠に、初回のcomposition時に与えたonStartと onStopを実行し続けてしまう。 
lifecycleOwner(Effectのkey)は変更されていないが、onStartもしくはonStopが変更されて、 HomeScreenのReCompositionが起動した場合がこれにあたる。

# rememberCoroutineScope
Composable関数内で使用するCoroutineScopeを生成する時に使用。
このScopeはRecompositionを跨いで生存する。

```kotlin:
@Composable
fun Screen(scaffoldState: ScaffoldState = rememberScaffoldState()) {

    val scope = rememberCoroutineScope()

    Scaffold(scaffoldState = scaffoldState) {
        Column {
            Button(
                onClick = {
                    scope.launch {
                        scaffoldState.snackbarHostState
                            .showSnackbar("")
                    }
                }
            ) {
                Text("スナックバー表示")
            }
        }
    }
}
```
# SideEffect
Compositionの成功後(recomposition含む)に必ず実行したい副作用を定義する。

```kotlin:
@Composable
fun rememberAnalytics(user: User): FirebaseAnalytics {
    val analytics: FirebaseAnalytics = remember { MockFirebaseAnalytics() }

    // composition成功後に必ず実行される
    SideEffect {
        analytics.setUserProperty("userType", user.userType)
    }

    return analytics
}
```
# produceState
非同期処理(Coroutine、Rxjava、コールバック関数等)の「実行~値の取得までの流れ」をStateクラスで表現できる。
Composable関数外の状態をCompose内のStateとして取り込む際に利用できる。

```kotlin:
@Composable
fun loadNetworkImage(
    url: String,
    imageRepository: ImageRepository
): State<Result<HiResImage>> {

    return produceState(
        initialValue = Result.Loading<HiResImage>() as Result<HiResImage>,
        url,
        imageRepository
    ) {
        // produceState内で実行するこの関数はCoroutineScope内で実行されるため、suspend関数の実行も可能
        val hiResImage = imageRepository.loadAsync(url).first()
        this.value = if (hiResImage == null) {
            Result.Error("読み込み失敗")
        } else {
            Result.Success(hiResImage)
        }
    }
}
```
# derivedStateOf
あるStateに紐づいた新しいStateを作成するときに使う。
derivedStateOfで定義したラムダ式内で、何らかのStateを参照していた場合、
そのStateが更新される度にderivedStateの値を更新される。

recompositon時のパフォーマンス改善で利用することが多い。
(derivedStateOfを利用するとこで、recompositionのたびに式が実行される事態を防ぐことができる。)

```kotlin:
@Composable
fun derivedExample() {
    val rootState: MutableState<String> = remember { mutableStateOf("A") }
    
    /** derivedStateOfに渡したラムダ式はrootStateが更新される度に評価され、
     * rootStateの更新に紐づいてderivedStateの値が更新される。*/
    val derivedState: State<Boolean> = remember() { derivedStateOf() { rootState.value == "A" } }
}
```