One Off the Slack: Beware Unplanned Composition Loops

Bradley Campbell discovered that one of his composables was resulting in an infinite composition loop:

@Composable
fun App(component: AppComponent) {
  val authRepository = component.authRepository()

  when (val authState = authRepository.state().collectAsState().value) {
    is AuthState.Authorized -> {
      // show login flow
    }
    is AuthState.Authenticated -> {
      // show user flow
    }
  }
}

It would obtain a state that triggered a recomposition, that obtained a state that triggered a recomposition, that obtained a state that triggered a recomposition, that…

The problem, as it turned out, was that due to a bug: component.authRepository() was returning a new object each time. As a result, authRepository.state().collectAsState() resulted in a new tracked state object every time, and that confused the composition code. States — whether managed by by state {} or collectAsState() or other similar options — rely upon object identity to work across repeated invocations of the same composition.

Bradley discovered the problem with some logging. This sort of thing feels like a common problem that we will run into with Compose development. However, repeated invocations of the same composition are a feature, not a bug. As Zach Klippenstein pointed out:

I wonder how you’d distinguish a composition loop from a composable that’s being animated, which i think would look the same (new composition on every frame)

Coming up with techniques to identify planned composition loops and unplanned composition loops may be something that we will need before long.


Read the original thread in the kotlinlang Slack workspace. Not a member? Join that Slack workspace here!