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

Manuel Lorenzo

11/04/2020, 11:07 PM
hi everybody! I don’t know if this has already been asked around here, but here’s my question: I have a composable which is the one inside my main content, that has a scaffold with a top bar, a content and a bottom navigation bar. I’m creating more composables in the content part, and in this case I have a
LazyColumnFor
, and I’m also using the navigation for compose. My question is that when I click on an item from the composable that has the
LazyColumnFor
I’m navigating to the detail composable of that item (let’s call it
ItemDetail
) and here, I see that the content is between the top bar and the bottom nav bar of the scaffold of the parent component. Is it possible to have a reference to the top bar and the bottom nav bar? For example I want to add the back arrow to the top bar for the up navigation. Thanks in advance!
z

Zach Klippenstein (he/him) [MOD]

11/04/2020, 11:28 PM
Your navhost is a child of the scaffold?
m

Manuel Lorenzo

11/04/2020, 11:29 PM
yes; it’s actually created inside the content of the bottomBar
I did it based on
Copy code
val navController = rememberNavController()
Scaffold(
    bottomBar = {
        BottomNavigation {
            val navBackStackEntry by navController.currentBackStackEntryAsState()
            val currentRoute = navBackStackEntry?.arguments?.getString(KEY_ROUTE)
            items.forEach { screen ->
                BottomNavigationItem(
                    icon = { Icon(Icons.Filled.Favorite) },
                    label = { Text(stringResource(screen.resourceId)) },
                    selected = currentRoute == screen.route,
                    onClick = {
                        // This is the equivalent to popUpTo the start destination
                        navController.popBackStack(navController.graph.startDestination, false)

                        // This if check gives us a "singleTop" behavior where we do not create a
                        // second instance of the composable if we are already on that destination
                        if (currentRoute != screen.route) {
                            navController.navigate(screen.route)
                        }
                    }
                )
            }
        }
    }
) {

    NavHost(navController, startDestination = Screen.Profile.route) {
        composable(Screen.Profile.route) { Profile(navController) }
        composable(Screen.FriendsList.route) { FriendsList(navController) }
    }
}
z

Zach Klippenstein (he/him) [MOD]

11/04/2020, 11:44 PM
Unless I’m reading something wrong, it looks to me like your NavHost is in the main body slot of the scaffold, not the bottom bar
You can read the current back stack entry as state to figure out what additional ui to put in your top and bottom bars as well as using it for the bottom navigation
i

Ian Lake

11/04/2020, 11:55 PM
Yep, the whole reason you use
rememberNavController()
outside of the NavHost itself is to hook up other things to changes in the NavController
So just like how your bottom bar reacts to changes and calls
navigate()
, so can your top bar show/hide an arrow if you are on the start destination of your graph and hook that up to
navController.navigateUp()
a

Archie

11/05/2020, 4:10 AM
Hi @Ian Lake, in relation to this.. doing something like this, is correct right?
Copy code
val navController = rememberNavController()
val navBackStackEntry by navController.currentBackStackEntryAsState()

Scaffold(
    bottomBar = {
        //----------- This right here -------//
        val isBottomBarVisible = navBackStackEntry?.arguments?.getString(MY_KEY) 
        //----------- This right here -------//

        if (isBottomBarVisible) {
            MyBottomBar()
        }
    }
) {
    NavHost(navController, startDestination = Screen.Profile.route) {  
        composable(Screen.Profile.route) { 
            Profile(navController) 
        }
        composable(
            Screen.MyScreen.route + "?${MY_KEY}=MY_KEY",
            ....
        ) { 
            MyScreen(navController) 
        }
    }
}
I figured I could also do something like this and achieve the same...
Copy code
val navController = rememberNavController()
val navBackStackEntry by navController.currentBackStackEntryAsState()
val isBottomBarVisible by rememeber { mutableStateOf(false) }

Scaffold(
    bottomBar = {
        if (isBottomBarVisible) {
            MyBottomBar()
        }
    }
) {
    NavHost(navController, startDestination = Screen.Profile.route) {  
        composable(Screen.Profile.route) { 
            Profile(navController) 
        }
        composable(
            Screen.MyScreen.route
        ) { 

            //----------- This right here -------//
            onCommit(navBackStackEntry) {
                isBottomBarVisible = true
            }
            //----------- This right here -------//

            MyScreen(navController) 
        }
    }
}
Was wondering if both approaches are valid?
i

Ian Lake

11/05/2020, 4:32 AM
Well, the former is using a single source of truth to automatically recompose your bottom nav in the same recomposition that the current destination changes and swaps from visible to not visible and back again while the later is building a whole other state that is set to true as a side effect of one destination being composed, forcing a second recomposition (since onCommit happens after the first composition finishes) and then never set back to false. So no, those aren't equivalent at all
a

Archie

11/05/2020, 4:34 AM
I see... thank you very much!
m

Manuel Lorenzo

11/05/2020, 9:29 AM
thanks a lot guys!
one thing I don’t get is that
navController.navigateUp()
from this detail view returns false and doesn’t navigate back
ok I understood it now: I need to pass the navController to my composable, and that was I can make it work 😄
what about Crossfade? how do animations work with navigation in this case? is there any sample to take a look at?
a

Archie

11/05/2020, 10:33 AM
@Ian Lake How about for things that couldn't be passed in as parameter? like Callbacks?
Copy code
val navController = rememberNavController()
var onCloseClick: (() -> Unit)? = null

Scaffold(
    topBar = {
        TopAppBar(
            ...,
             actions = {
                 IconButton(onClick = {
                     onCloseClick?.invoke()
                  }) {
                      Icon(asset = Icons.Default.Close)
                   }
             }
         )
     },
) {
    NavHost(
        navController = navController,
        startDestination = ScreenA.route,
    ) {
        composable(startingRoute) {
            // ------- This here ------- //
            onCommit(currentBackStackEntry) {
                onCloseClick = {
                    // Do something
                }
            }
            onDispose {
                onCloseClick = null
            }
        }
        composable(otherRouter) {
            // ------- This here ------- //
            onCommit(currentBackStackEntry) {
                onCloseClick = {
                    // Do something else
                }
            }
            onDispose {
                onCloseClick = null
            }
        }
    }
}
@Manuel Lorenzo, there is a thread where they are discussing about that but I can't find it.. ill link it here once i see it.
m

Manuel Lorenzo

11/05/2020, 11:45 AM
thanks man!
i

Ian Lake

11/05/2020, 5:28 PM
No animation support as of yet - the previous thread we discussed animations in was https://kotlinlang.slack.com/archives/CJLTWPH7S/p1604093221008800, the issue you can star is https://issuetracker.google.com/172112072
m

Manuel Lorenzo

11/12/2020, 7:47 AM
Thanks a lot for the info @Ian Lake and keep up the good work! 👏