cashapp / molecule

Build a StateFlow stream using Jetpack Compose
https://cashapp.github.io/molecule/docs/1.x/
Apache License 2.0
1.78k stars 76 forks source link

RecompositionMode.Immediate with non-ui dispatcher can cause "missed" recompositions #416

Open j2esu opened 2 months ago

j2esu commented 2 months ago

I've noticed that if molecule composition happens together with ui composition the molecule composition is "missed". It never happens with the ContextClock mode but it's quite easy to reproduce with the Immediate mode and non-ui dispatcher.

Below there is an example of counter that updates the local state and sends a message to update the state produced by molecule. In 5-15 clicks (no need to click fast though) it's possible to get the wrong state when the states are not equal. The next click after that usually makes them equal again (which I think means that the state in molecule gets updated, but it doesn't trigger a recomposition, probably because the ui recomposition is currently in progress).

Screenshot 2024-04-07 191342

Molecule 1.4.2 Compose compiler 1.5.11 Compose runtime 1.7.0-alpha06 (to get #396 fixed) Android SDK 34

class MainActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val messages = MutableSharedFlow<Unit>(extraBufferCapacity = 10)
        val state = lifecycleScope.launchMolecule(RecompositionMode.Immediate, Dispatchers.Default) {
            var count by remember { mutableIntStateOf(0) }
            LaunchedEffect(Unit) {
                messages.collect { count++ }
            }
            count
        }
        setContent {
            val count by state.collectAsState()
            var countLocal by remember { mutableIntStateOf(0) }
            Column(modifier = Modifier.background(Color.Black)) {
                Text(text = "Count: $count", color = Color.White)
                Text(text = "Count local: $countLocal", color = Color.White)
                Text(
                    text = "Click me",
                    color = Color.White,
                    modifier = Modifier
                        .clickable {
                            countLocal++
                            messages.tryEmit(Unit)
                        }
                )
            }
        }
    }
}
j2esu commented 1 month ago

It's much harder to reproduce (takes 50-100 clicks) with molecule 2.0.0, compose 1.7.0-beta01 and compose compiler shipped with kotlin 2.0.0