Side Effects Summary in Jetpack Compose
Tags: android, kotlin, beginners, jetpackcompose
Published: 2023-02-10 23:00:00

**Do you find side effects in Jetpack Compose confusing? I do. So, I document the summary of using side effects here for my future reference.**

A [side effect](https://developer.android.com/jetpack/compose/side-effects) in Compose is a change to the state of the app that happens outside the scope of a composable function.

To handle these side effects, you can use effect handlers. There are 2 types of effect handlers.

| Suspended Effect Handler | Non-suspended Effect Handler |
| --- | --- |
| LaunchedEffect() | DisposableEffect() |
| rememberCoroutineScope() | SideEffect() |

## Suspended Effect Handler

A suspended effect handler allows you to perform side effects in compose using [coroutines](https://vtsen.hashnode.dev/kotlin-coroutines-basics-simple-android-app-demo).

Here is the summary that covers different scenarios:

| Scenarios | LaunchedEffect() | rememberCoroutineScope() |
| --- | --- | --- |
| Launch effect/coroutine from within a composable? | Yes | No |
| Launch effect/coroutine outside a composable? E.g. from the callback | No | Yes |
| When effect/coroutine is started? | `LaunchedEffect()` enters composition | `CoroutineScope.launch()` is called |
| When effect/coroutine is canceled? | `LaunchedEffect()` leaves composition, `LaunchedEffect()` is restarted (effect's key changed) | `CoroutineScope` leaves composition. ***Note:*** *When coroutine is restarted, the previous coroutine will NOT be canceled* |
| When effect/coroutine is restarted? | `LaunchedEffect()`'s key changed (while it is still in composition) | `CoroutineScope.launch()` is called again |

## Non-suspended Effect Handler

For non-suspended/non-coroutine side effects, you can use these non-suspended effect handlers.

| Scenarios | DisposableEffect() | SideEffect() |
| --- | --- | --- |
| Launch effect from within a composable? | Yes | Yes |
| Launch effect outside a composable? E.g. from the callback | No | No |
| When effect is started? | `DisposableEffect()` enters composition, after the current composition completes | `SideEffect()` enters composition, after the current composition completes |
| When effect is canceled? | Effect can NOT be canceled, since the execution can NOT be suspended(non-suspended function) | Effect can NOT be canceled - same as `DisposableEffect()` |
| When effect is restarted? | `DisposableEffect()`'s key changed (while it is still in composition) | `SideEffect()` enters recomposition - **every recomposition triggers the** `SideEffect()` to run |
| When `onDispose()` is called? | `DisposableEffect()` leaves composition, `DisposableEffect()` is restarted (effect's key changed) ***Note:*** *When the effect completes, it will NOT trigger* `onDispose()` | N/A |

## Various Side-effect States

These are the various compose state helpers for effect handlers above.

### rememberUpdatedState

`rememberUpdatedState` makes sure the `MutableState` is always updated with the latest value instead of caching the initial composition value.

See notes (1), (2), (3), (4) and (5) below.

```kotlin
@Composable
fun RememberUpdatedStated(value: String) {
    val oldValue by remember { mutableStateOf(value) }
    val newValue by rememberUpdatedState(value)

	// (2) LaunchedEffect is skipped during the second recomposition
	//	   when value is changed/updated
    LaunchedEffect(true) {
        // (1) let's assume value is updated with a new value within 1 second delay
        delay(1000)
		// Access value, oldvalue and newValue here
		        
        // (3) value is the initial value when LaunchedEffect is first called
        // (4) oldValue is the initial value from first composition
        // (5) newValue is the new value from second recomposition        
    }
}
```

> Only the `newValue` has the latest updated `value` from the second recomposition

If you look at the `rememberUpdatedState` implementation, it applies the new value to the `MutableState` whenever it is called or during recomposition.

```kotlin
@Composable  
fun <T> rememberUpdatedState(newValue: T): State<T> = remember {  
  mutableStateOf(newValue)  
}.apply { value = newValue }
```

Technically,

```kotlin
val newValue by rememberUpdatedState(value)
```

is equivalent to

```kotlin
var newValue by remember { mutableStateOf(value) }  
newValue = value
```

### produceState

`produceState` is the sugar syntax for `LaunchedEffect()`.

`LaunchedEffect()` below can be written as

```kotlin
@Composable
fun DemoLaunchedEffect(){
    var text by remember { mutableStateOf("")}

    LaunchedEffect(true) {
        repeat(10) { count ->
            delay(1000)
            text = count.toString()
        }
    }
}
```

`produceState()` below:

```kotlin
@Composable
fun DemoProduceState(){
    val text by produceState(initialValue ="") {

        repeat(10) { count ->
            delay(1000)
            value = count.toString()
        }
    }
}
```

One small difference of `produceState` is it produces `State<T>` instead of `MutableState<T>`. Thus, you see the `text` above is declared with `val` instead of `var`.

One additional thing `produceState` can do is `awaitDispose()` function, which allows you to detect when the `produceState` leaves the composition. This is similar to `onDispose()` from `DisposableEffect()`

```kotlin
@Composable
fun DemoProduceStateAwaitDispose(){
    val text by produceState(initialValue ="") {
        val job = MainScope().launch {
            repeat(10) { count ->
                delay(1000)
                value = count.toString()
            }
        }

        awaitDispose {
            job.cancel()
        }
    }
}
```

> However, to use `awaitDispose()` you MUST manually launch a coroutine with a `CoroutineScope`. The following example uses the `MainScope()`.

Without the `MainScope()` or any `CorutineScope`, the `awaitDispose()` will not be called. For example, the following code won't work.

```kotlin
@Composable
fun DemoProduceStateAwaitDispose(){
    val text by produceState(initialValue ="") {

        repeat(10) { count ->
            delay(1000)
            value = count.toString()
        }
    
        // Won't work - awaitDispose() won't be called
        awaitDispose {
            job.cancel()
        }
    }
}
```

> For some reason, this requirement is not documented. It takes me a while to figure out the `awaitDispose()` requires you to launch the coroutine manually to get it working correctly.

### derivedStateOf

Not sure why this is categorized under side effects, but what `derivedStateOf()` does, is combining multiple states into a single `State`.

```kotlin
@Composable
fun DemoDerivedStateOf() {
    var value1 by remember { mutableStateOf(true) }
    var value2 by remember { mutableStateOf(false) }
    
    val derivedValue by remember(value1) {
        derivedStateOf {
            "value1: $value1 + value2: $value2"
        }
    }
}
```

When `value1` or `value2` is changed, `derivedValue` is updated. The `remember(value1)` is needed so that, during recomposition, `derivedStateOf()` is skipped.

If you remove the `remember(value1)` as the following, everything still works correctly.

```kotlin
@Composable
fun DemoDerivedStateOf() {
    var value1 by remember { mutableStateOf(true) }
    var value2 by remember { mutableStateOf(false) }
    
    val derivedValue by derivedStateOf {
	    "value1: $value1 + value2: $value2"        
    }
}
```

However, during every recomposition, this line `"value1: $value1 + value2: $value2"` is executed. Thus, it simply wastes unnecessary resources here.

### snapShotFlow

`snapShotFlow` converts `State<T>` to `Flow<T>`. Here is an example:

```kotlin
@Composable
fun DemoSnapShotFlow(){
    var textState =  remember { mutableStateOf("") }

    LaunchedEffect(textState) {
        // Convert State<T> to Flow<T>
        val flow = snapshotFlow { textState.value } 
        // Ensure flow doesn't emit the same value twise
        flow.distinctUntilChanged()
        // Collect the flow
        flow.collect { text ->
            Log.d("[SnapShotFlow]", "collecting flow value: $text")  
        }
    }
}
```

For more information about collecting flow, refer to the following article:

- [Exploring Different Ways to Collect Kotlin Flow](https://vtsen.hashnode.dev/exploring-different-ways-to-collect-kotlin-flow)

## Conclusion

I just merely document these side effect handlers' usages and their behaviors. However, I haven't known how to use them effectively yet. Sometimes I do find it confusing which one to use. :)

## Source Code

This is my experimental code of playing around with side effects in Jetpack Compose. So it might be a bit messy here.

GitHub Repository: [Demo\_ComposeSideEffects](https://github.com/vinchamp77/Demo_ComposeSideEffects)

---

*Originally published at [https://vtsen.hashnode.dev](https://vtsen.hashnode.dev/side-effects-summary-in-jetpack-compose)*.