PreCompose icon indicating copy to clipboard operation
PreCompose copied to clipboard

[BUG] ModalBottomSheet with nested NavHost breaks the Composition

Open tomczyn opened this issue 1 year ago • 2 comments

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
    ...
}

tomczyn avatar Apr 17 '24 17:04 tomczyn

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

Tlaster avatar Apr 18 '24 02:04 Tlaster

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

tomczyn avatar Apr 18 '24 05:04 tomczyn