compose-multiplatform icon indicating copy to clipboard operation
compose-multiplatform copied to clipboard

On JS and Wasm (and maybe iOS), when depending a `val` storing the state value outside in a `NavHost` `composable` lambda, the updated state is not propagated

Open ShreckYe opened this issue 1 year ago • 0 comments

Describe the bug

See the code below:

val (count, setCount) = remember { mutableStateOf(0) }
val navController = rememberNavController()
val clickToIncCountModifier = Modifier.onClick {
    setCount(count + 1)
}

Column {
    BasicText("Count outside: $count", clickToIncCountModifier)
    NavHost(navController, "0") {
        repeat(4) { i ->
            composable(i.toString()) {
                Column {
                    BasicText("Screen $i")
                    BasicText("Count in `NavHost`: $count", clickToIncCountModifier)
                }
            }
        }
    }
}

Run the app on Wasm, and click the "count" texts to increment the text. If you click the text outside the NavHost, the value is correctly incremented; if you click the text inside, the value is always reset to 1 which is the original value 0 incremented. However, If I use MutableState.value directly in the lambda or use delegated properties for the state instead, it works. Also both texts increment and show the count correctly on JVM desktop.

After going through a bunch of Jetpack Compose docs and experimenting with the code myself, I suspect that this is a bug with the Compose compiler and the navigation library combined, firstly because it works on JVM desktop but not on JS and Wasm, and secondly because I see that lambdas are stable, so they are immutable and essentially closures wrapping val count and their recomposition is skipped, and while the compiler may have adopted some mechanism similar to rememberUpdatedState on desktop, it doesn't do it on JS or Wasm. However, LazyColumn has a similar style of adding composable lambdas in a non-composable lambda, but doesn't have this bug as I have tested. Nevertheless, These are merely my thoughts and of course you guys are the experts, so correct me if I am wrong.

I came across this problem when I was trying to port a small portion of Compose navigation to Compose HTML. And I spent quite some time debugging this. If you are interested in more details, see the commit messages of the latest 4 commits here.

Affected platforms

  • maybe iOS too (I didn't check)
  • Web (K/Wasm) - Canvas based API
  • Web (K/JS) - Canvas based API
  • Web (K/JS) - HTML library (NavHost is not available on this, I copied and adapted a NavHost myself and it still occurs. See https://github.com/huanshankeji/compose-multiplatform-material/commit/3c48921f8b89fd21c5f3227c463b62458c7df567 for details.)

Versions

  • Libraries:
    • Compose Multiplatform version: 1.6.10 & 1.6.11
    • Navigation Multiplatform version (for related issues): 2.7.0-alpha07
  • Kotlin version: 2.0.0
  • OS version(s) (required for Desktop and iOS issues): Ubuntu 22.04.4 LTS
  • OS architecture (x86 or arm64): x86
  • JDK (for desktop issues): OpenJDK Runtime Environment (build 21.0.3+9-Ubuntu-1ubuntu122.04.1) for Gradle, some JDK 8 for runtime because I set jvmToolchain(8)

To Reproduce

See the code and instructions above.

ShreckYe avatar Jun 14 '24 02:06 ShreckYe