Ajay Chandran
Ajay Chandran

Reputation: 264

Recomposition while using rememberCoroutineScope

I am using a Coroutine Scope inside a callback for navigation icon selection. But whenever the screen recompose, this icon is also getting recomposed because of the coroutine scope used in the callback.

But if i use a state variable and update the LaunchedEffect, it is skipping the recomposition.

A sample code for both case is given below: (Compose Version: 1.3.1)

Case 1: MethodTwo gets recomposed on every recomposition of MethodOne.

@Composable
fun MethodOne() {
   val scope = rememberCoroutineScope()

   MethodTwo(onClick = {
     scope.launch { //viewmodel actions }
   })


@Composable
fun MethodTwo(onClick: () -> Unit) {
   //UI
}

Case 2: MethodTwo skips the recomposition on every recomposition of MethodOne.

@Composable
fun MethodOne() {
   var toggle = remember { mutableStateOf(false) }

   LaunchedEffect(toggle) {
      //viewmodel actions
   }

   MethodTwo(onClick = {
     toggle = true
   })


@Composable
fun MethodTwo(onClick: () -> Unit) {
   //UI
}

Is anyone facing the same issue? Is there any workaround or am i missing something on recomposition?

Upvotes: 0

Views: 380

Answers (1)

hasan.z
hasan.z

Reputation: 349

The main reason that causes unnecessary recompositions is related to the stability of lambdas, not using scope.

This issue is resolved with no extra effort required by enabling Strong Skipping Mode, available as an experimental feature in Jetpack Compose Compiler 1.5.4+ and enabled by default in Kotlin 2.0.20.

In short, Strong Skipping Mode modifies the Compose compiler’s behavior by:

  • Making composables with unstable parameters skippable.
  • Remembering lambdas with unstable captures.

Key References:

  • Strong skipping mode (Official Documentation)
  • Jetpack Compose: Strong Skipping Mode Explained by Ben Trengrove Senior Developer Relations Engineer at Google (Medium)
  • New Ways of Optimizing Stability in Jetpack Compose by Tomáš Mlynarič Android Developer Relations Engineer at Google (Medium)

Performance Issue Explained:

As mentioned earlier, by using Strong Skipping Mode no extra effort required, but if you're wondering what that performance issue was, continue to read:

Here is, the MethodTwo:

@Composable
fun MethodTwo(onClick: () -> Unit) {
   // UI
}

By using Compose compiler report we see this composable is restartable and skippable with stable onClick parameter:

restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun MethodTwo (
  stable onClick: Function1<Unit>
)

As you can see, the lambda itself is stable. So, everything depends on how it is used. Here is a section of the article Jetpack Compose: Strong Skipping Mode Explained:

Lambdas that capture stable values prevent unnecessary recompositions. However With Strong Skipping Mode, even lambdas with unstable captures are memoized and skippable. This means all restartable composable become skippable, even with unstable parameters.

Detailed Explanation: But what's problems with your cases:

  • In Case 1, if you use an unstable ViewModel in the onClick callback, the lambda captures an unstable value (For more on stability, refer to this Android stability guide). Kotlin lambdas at the end are converted to Function objects, so every recomposition causes the onClick lambda to be reallocated. Since the Compose compiler uses instance equality (===) to compare unstable parameters, the composable is recomposed because the lambda’s object reference changes, even if the captured value hasn’t changed.
  • In Case 2, the lambda captures stable actions, which avoids unnecessary recompositions. This happens because stable parameters are compared to their previous values using object equality (Object.equals()). As a result, the lambda doesn’t change between recompositions, allowing Compose to skip re-evaluating the function.

Upvotes: 1

Related Questions