https://kotlinlang.org logo
#compose
Title
# compose
a

Alexander Karkossa

10/13/2020, 12:31 PM
Hey guys, i have a question. I tried to use an app-wide Scaffold with NavHost (from the navigation Snapshot Library) in its content. Each view should be able to edit the state of the bottomAppBar and the fab. (Show Fab, disable BottomDrawerLayout Gesture, hide BottomAppBar and so on) I tried the following, but i doesn't work. When i try to navigate to the LoginScreen, the changes in the bottomAppBarConfig and the fabConfig are triggering a complete redraw and the NavHost will also be recreated. Even before i try to navigate the hole tree recreates in a loop because of the config change for the first view How to do it correctly? I dont want to set the Scaffold per view because the app contains nearly 40 views...
a

Afzal Najam

10/13/2020, 12:49 PM
Can you try
val fabConfig = remember { mutableStateOf(FabConfig()) }
? Same with bottomAppBar config.
a

Alexander Karkossa

10/13/2020, 12:52 PM
Does not Help. All fields in the FabConfig and the BottomAppBarConfig are MutablesStates as well
a

allan.conda

10/13/2020, 1:53 PM
Put navHost controller inside scaffold. Put bottomAppBarConfig and fabConfig inside BottomDrawerLayout
a

Alexander Karkossa

10/13/2020, 2:05 PM
That doesn't work, because i need to know if the bottomAppBar is hidden to disable the gestures in the BottomDrawerLayout. So the Config for the BottomAppBar needs to be outside of BottomDrawerLayout
a

allan.conda

10/13/2020, 2:16 PM
Ah, right, keep the bottom config above
a

Alexander Karkossa

10/13/2020, 2:27 PM
I think i found a solution
a

Afzal Najam

10/13/2020, 2:29 PM
@Alexander Karkossa So I just tried this and it works for me. The redraw log only prints once, even if I navigate away and the Fab hides and shows just fine too.
Copy code
@Composable
private fun BottomDrawerApp() {
    val navController = rememberNavController()
    val fabConfig = remember { FabConfig() }

    Log.d("TAG", "redrawing")
    BottomDrawerLayout(drawerContent = {}) {
        Scaffold(
            floatingActionButton = {
                if (fabConfig.isVisible.value) {
                    FloatingActionButton(onClick = {}) {

                    }
                }
            },
            bodyContent = {
                NavHost(
                    navController = navController,
                    startDestination = Screen.Profile.title
                ) {
                    composable(Screen.Profile.title) {
                        Button(onClick = {
                            navController.navigate(Screen.Dashboard.title)
                        }) {
                            Text(text = "Dashboard")
                        }
                        fabConfig.isVisible.value = true
                    }
                    composable(Screen.Dashboard.title) {
                        Column(modifier = Modifier.fillMaxSize().then(Modifier.padding(8.dp))) {
                            Text(text = Screen.Dashboard.title)
                        }
                        fabConfig.isVisible.value = false
                    }
                    composable(Screen.Scrollable.title) {
                        NoClickScrollable()
                    }
                }
            }
        )
    }
}

class FabConfig(val isVisible: MutableState<Boolean> = mutableStateOf(true))
Mind you, I kind of don't agree with a mutable FabConfig object, but I guess that's your choice.
a

Alexander Karkossa

10/13/2020, 2:37 PM
Your solution worked because u removed some of my functionality. Try to add gestureEnabled on BottomDrawerLayout and set the value in both views, like the fab visablity
a

Afzal Najam

10/13/2020, 2:43 PM
hmm, I'm seeing a weird issue only when using:
Copy code
isFloatingActionButtonDocked = fabConfig.isVisible.value
If I set it to true, the issue disappears.
a

Alexander Karkossa

10/13/2020, 2:44 PM
I think floatingActionButtonPosition also triggers these issue, simillar to gestureEnabled
a

Afzal Najam

10/13/2020, 2:45 PM
Yeah, it seems like using mutableState for these is causing this issue but only if there's a NavHost.
a

Alexander Karkossa

10/13/2020, 2:46 PM
I thinks its because the Scaffold is recomposed and its childs are also recomposed. When u change the fab visability, only the inner composable for the fabs is recomposed
The NavHost is also recreated and than u are on the startDestination again.
Copy code
@Composable
fun AppContent() {
    val navHostController = rememberNavController()

    NavHost(navController = navHostController, startDestination = Screen.CheckLicenseScreenDestination) {
        composable(id = Screen.CheckLicenseScreenDestination) {
            NavContent { fabConfig: FabConfig, bottomAppBarConfig: BottomAppBarConfig ->
                onCommit {
                    fabConfig.isVisible.value = false
                    bottomAppBarConfig.showBottomBar.value = false
                }

                Column(modifier = Modifier.fillMaxSize()) {
                    Button(onClick = {
                        navHostController.navigate(Screen.LoginScreenDestination)
                    }) {
                        Text(text = "License Check done!")
                    }
                }
            }
        }
        composable(id = Screen.LoginScreenDestination) {
            NavContent { fabConfig: FabConfig, bottomAppBarConfig: BottomAppBarConfig ->
                onCommit {
                    fabConfig.isVisible.value = true
                    fabConfig.fabPosition.value = FabPosition.End
                    bottomAppBarConfig.showBottomBar.value = true
                }

                Column(modifier = Modifier.fillMaxSize()) {
                    Text(text = "Login View")
                }
            }
        }
    }
}

@Composable
fun NavContent(content: @Composable (fabConfig: FabConfig, bottomAppBarConfig: BottomAppBarConfig) -> Unit) {
    val fabConfig = remember { FabConfig() }
    val bottomAppBarConfig = remember { BottomAppBarConfig() }

    BottomDrawerLayout(
        gesturesEnabled = bottomAppBarConfig.showBottomBar.value,
        drawerContent = {}
    ) {
        Scaffold(
            modifier = Modifier.statusBarsPadding().navigationBarsPadding(),
            floatingActionButton = {
                if (fabConfig.isVisible.value) {
                    FloatingActionButton(
                        onClick = { }
                    ) {
                        Icon(
                            asset = vectorResource(id = R.drawable.ic_add_white_24dp),
                            tint = MaterialTheme.colors.onSecondary
                        )
                    }
                }
            },
            isFloatingActionButtonDocked = fabConfig.isVisible.value,
            floatingActionButtonPosition = fabConfig.fabPosition.value,
            bottomBar = {}
        ) {
            content(
                fabConfig = fabConfig,
                bottomAppBarConfig = bottomAppBarConfig
            )
        }
    }
}
Checkout these and tell me what u think
a

Afzal Najam

10/13/2020, 3:09 PM
I'll check that code later today yeah, you're right. The scaffold gets recomposed, causing all its children to compose again, which causes the first screen to reset the fab's visibility. With NavContent, you basically have a Scaffold for every view, which is what you wanted to avoid. I've been able to recreate this without NavHost too now.
Copy code
@Composable
private fun WeirdFabApp() {
    var showFab by remember { mutableStateOf(true) }

    Scaffold(
        floatingActionButton = {
            if (showFab) {
                FloatingActionButton(onClick = {
                    showFab = false
                }) {

                }
            }
        },
        bottomBar = {},
        floatingActionButtonPosition = FabPosition.Center,
        isFloatingActionButtonDocked = showFab,
        bodyContent = {
            onCommit {
                showFab = true
            }
            Button(onClick = {
                showFab = false
            }) {
                Text(text = "Dashboard")
            }
        }
    )
}
Maybe extending/wrapping Scaffold in your own version of Scaffold is the way to go (not sure that would work) and maybe ScaffoldState code would help figure this out.
4 Views