Tlaster / PreCompose

Compose Multiplatform Navigation && State Management
https://tlaster.github.io/PreCompose/
MIT License
852 stars 49 forks source link

[BUG] ModalBottomSheet with nested NavHost breaks the Composition #311

Open tomczyn opened 6 months ago

tomczyn commented 6 months ago

Describe the bug When nesting a NavHost inside a ModalBottomSheet content, it shows visual glitches instead of a correct Composables. By glitches I mean nested NavHost (bottom sheet content) is being displayed on the sheet and underneath the sheet, Compose is copying the content to two places and not displaying the original NavHost at all.

What it should show, blue is a main view (whole screen home NavHost), red is bottom sheet content (150.dp height).

Zrzut ekranu 2024-04-17 o 18 45 30

What is shown, bottom sheet content displayed on the sheet and underneath it.

Zrzut ekranu 2024-04-17 o 18 48 35

Minimal reproducible example

@Composable
fun App() {
    PreComposeApp {
        MaterialTheme {
            Column(modifier = Modifier.fillMaxSize()) {
                var showBottomSheet by remember { mutableStateOf(false) }
                val navigator = rememberNavigator()
                NavHost(
                    navigator = navigator,
                    navTransition = NavTransition(),
                    initialRoute = "/home",
                ) {
                    scene(route = "/home") {
                        Column(
                            Modifier.fillMaxSize().background(Color.Blue),
                            horizontalAlignment = Alignment.CenterHorizontally
                        ) {
                            Text("Main Title")
                            Button(onClick = { showBottomSheet = true }) {
                                Text("Show bottom sheet")
                            }
                        }
                    }
                }
                if (showBottomSheet) {
                    val sheetState = rememberModalBottomSheetState()
                    val scope = rememberCoroutineScope()
                    ModalBottomSheet(
                        sheetState = sheetState,
                        onDismissRequest = { showBottomSheet = false },
                        dragHandle = null,
                        content = {
                            val sheetNavigator = rememberNavigator()
                            NavHost(  // Sheet NavHost, when removed everything starts to work correctly
                                navigator = sheetNavigator,
                                navTransition = NavTransition(),
                                initialRoute = "/sheet",
                            ) {
                                scene(route = "/sheet") {
                                    Column(
                                        Modifier.fillMaxWidth().height(150.dp)
                                            .background(Color.Red),
                                        horizontalAlignment = Alignment.CenterHorizontally
                                    ) {
                                        Text("BottomSheet Title")
                                        Button(
                                            onClick = {
                                                scope.launch { sheetState.hide() }
                                                    .invokeOnCompletion {
                                                        if (!sheetState.isVisible)
                                                            showBottomSheet = false
                                                    }
                                            }
                                        ) {
                                            Text("Hide bottom sheet")
                                        }
                                    }
                                }
                            }
                        }
                    )
                }
            }
        }
    }
}

Repo: https://github.com/tomczyn/PreCompose-Sample Branch with StateHolder fix: https://github.com/tomczyn/PreCompose-Sample/tree/fix

Workaround

It appears that wrapping the NavHost in StateHolder solves the problem with displaying the content, but the behavior of the bottom sheet is still a little bit bugged with this workaround. Instead, it sometimes doesn't show the showing/hidding animation of the bottom sheet correctly.

val stateHolder = remember { StateHolder() }
CompositionLocalProvider(LocalStateHolder provides stateHolder) {
    NavHost( // Sheet NavHost
    ...
}
Tlaster commented 6 months ago

It appears that rememberNavigator consistently returns the same Navigator. One possible workaround is to assign a unique name each time rememberNavigator is used. Perhaps we should consider automatically assigning different names when using rememberNavigator

tomczyn commented 6 months ago

Thank you, that works! The most important thing is it can be very easily solved, but good point on the behavior change.