We have a `HideBottomNav()` composable that basica...
# compose
s
We have a
HideBottomNav()
composable that basically just wraps a
DisposableEffect
and sets a global
isBottomNavVisible
to true, then false again in
onDispose
. This has worked well for us so far, but we were toying with the idea of synchronizing its show/hide animation with the screen transitions from compose-navigation. I was thinking of changing this
isBottomNavVisible
property to something more of a
visibilityFraction
or something. However, I don't see a way to get the progress of transitioning between screens from the
AnimatedContentScope
that
composable {}
provides. Am I approaching this the wrong way?
s
Setting global vars inside effects in screens feels like a brittle way to go about this imo, yes. So does each destination know about if it should or shouldn't show the bottom nav in? What is the source of truth of if the bottom nav is or isn't showing?
2
And as a separate question, does grabbing the transition from your scope let you do anything interesting? Not that I'd recommend going this way (hence my first question), but still curious
s
By default, all destinations effectively show the navigation bar, and only certain screens would need to hide it. The navigation bar lives outside of the nav host, which is where this global config (in reality, it's a composition local provided at the same layer as the navigation bar). The navigation bar reads from this config and reacts to changes, and the
HideBottomNav()
composable effectively wraps a
DisposableEffect
, as mentioned above, which updates the config. We could potentially maintain a list of all the routes that should hide the navigation bar, but I still don't see how that would help. I've looked into getting stuff from the
transition
, but it doesn't seem to provide any progress for the transition. I think that might be on purpose, as it doesn't make sense for all transitions specs (
spring()
, for instance, comes to mind).
To fully answer your question (I hope): the “source of truth” is the bottom nav config (the composition local, whichever exists in the current composition). The bottom nav only gets info from that one, and it may get updated by screens further down the hierarchy.
👍 1
s
Perfect, if your situation is that all destinations want the bottom nav except for some, you got a very simple path to make your source of truth be your navigation state instead. Take a look at this https://github.com/HedvigInsurance/android/blob/156d5a6574d787ade803982ad2a57d87d5[…]app/src/main/kotlin/com/hedvig/android/app/ui/HedvigAppState.kt Use
navController.currentBackStackEntryAsState()
as the source of which screen is on the screen at any given moment. Get that destination and use the
public inline fun <reified T : Any> NavDestination.hasRoute() = hasRoute(T::class)
function to check if it's of the type of the routes that are excluded from the permitted list and return true or false depending on it. Then it's a matter of just using that as the source of truth for what you pass to that global place of showing/not showing as a direct replacement to that composition local.
thank you color 1
From your description it's a very clear case where you won't struggle a lot to make this happen. Feel free to ask questions if what I am saying does not make sense, or the code linked is hard to parse
s
You know what, this makes so much more sense than what we've been doing, and I'm totally going to go this route (pardon the pun) instead! Thanks a lot!
🥁 1
s
From that point, once your SOT is your nav state itself, you can observe that and do an animated hiding/showing of the navigation bar. Here's a video of how that looks like for us in the video:
The one thing which is not 100% simple to do atm is that as you got the back gesture seek back to the previous screen, it is not settled yet, so your SOT still says "no, don't need to show it yet". I've filed this https://issuetracker.google.com/issues/331809442 to perhaps allow for more cool things here. Perhaps one could pull this off by making a custom NavHost() instead of using the built-in one by copy-pasting it and finding a way to expose the progress state too, but I've never looked into doing that myself
s
That's funny, cause that's actually what started this whole journey for us; being able to animate the navigation bar alongside the predictive back. Starred your issue, hoping that'll be available Soon™.
We actually started this by attempting to set our global bottom nav state (🤮) in a
PredictiveBackHandler
, but it turns out that multiple of those don't necessarily work great together.
s
Yeah it can be tricky. Thanks for the star, I also hope there's some movement there. But if you really want to make this happen, you should look into perhaps making a custom NavHost. Note, I've never tried this myself, but I saw this CL the other day https://android-review.googlesource.com/c/platform/frameworks/support/+/3064264 of how it's possible to just make your own implementation of this to make other cool things. So seeing this makes me thing one could totally take the existing NavHost and copy-paste the sources and add some code inside the PredictiveBack call in there to also update some value which you can later read for these purposes. From my understanding this is also just built on top of existing public API which you can also utilize to make this happen.
s
That's cool, thanks for the link! However, I don't think we want it that badly, tbh, it was primarily just a thing we thought could be interesting to make work. Interesting stuff to read through, though!
a
If you decide to go the global variable route, you could try the following code:
Copy code
fun NavGraphBuilder.myComposable(
    route: String,
    bottomNavVisible: Boolean = false,
    arguments: List<NamedNavArgument> = emptyList(),
    deepLinks: List<NavDeepLink> = emptyList(),
    content: @Composable AnimatedContentScope.(NavBackStackEntry) -> Unit,
) {
    composable(
        route = route,
        arguments = arguments,
        deepLinks = deepLinks,
        content = {
            val bottomNavigationHost = LocalBottomNavigationHost.current
            val targetState = transition.targetState
            val currentState = transition.targetState
            LaunchedEffect(targetState) {
                if (targetState == EnterExitState.Visible || currentState == EnterExitState.PreEnter) {
                    bottomNavigationHost.setBottomNavVisible(bottomNavVisible)
                }
            }
            content(it)
        },
    )
}
it kinda solves @Stylianos Gakis’s problem of > The one thing which is not 100% simple to do atm is that as you got the back gesture seek back to the previous screen, it is not settled yet, so your SOT still says "no, don't need to show it yet".
s
What if you start a back gesture, the bottom nav dismisses as you're trying to go there, but then cancel out the back gesture? You will have dismissed the nav bar then in the wrong destination, no?
a
that's why I have the targetState check
that's why I have the targetState check. the problem here is the reverse of yours: it does show the bar while you're seeking, but if you cancel the seek it will wait for the transition to complete before hiding it back.
s
Haha always some edge case which is tricky huh 😅 It just can never be perfect 😄
😁 1