Does this seem like normal navigation behavior wit...
# compose-android
b
Does this seem like normal navigation behavior with the keyboard dismissal? The keyboard automatically dismisses a second or two after the navigation has already happened. This happens on my personal app with NavHosts, NavGraphs etc as well as the app I'm working for at my company with a similar setup. Surely we shouldn't have to call keyboardController?.hide() when performing navigation events, right? I mean the keyboard is automatically dismissing, it's just so delayed.
s
What kind of behavior did you expect here? The old screen still exists as it animates out. When the animation ends, there is nothing focused therefore the keyboard exits. You can probably understand this behavior better if you remove the animation and navigate immediately to the new screen (no crossfade either, an immediate transition) and you'll see the keyboard leaving immediately as well, as the focused element exits composition
👆 2
b
Stylianos would you expect that users would be fine with this behavior, or as a developer would it make sense to combat this behavior? I.e. do you think this is normal and wouldn't be cause for concern, or do you think the majority will want to manually hide the ime before navigating to avoid this?
s
That's not really my question. If I had such an interaction in my app and I felt like it didn't feel right to be this way I'd make sure to clear the focus as soon as the continue button was pressed. But I'm still curious how you would want this to automatically happen for you but only for the navigation case, since focus and IME handling and navigation are two completely separate things. But maybe I'm missing something that you've thought of, hence my question 😅
b
I've just noticed in other apps like Reddit, if you've typed a search into the search bar and clicked on one of the results the keyboard dismisses relatively quickly. I'm just using the default compose animations for my personal app at least, not sure if the animation is the problem I'm having, or what steps I would want to take to get that to be more seamless. Reddit for example doesn't remove focus on a field when you dismiss the IME, as is the default behavior of a TextField on Android, so I'm not sure if I should be explicitly unfocusing any textfield when a button is pressed that will cause a navigation action or if I should be calling dismiss on the keyboardController when pressing a button that will cause a navigation action or even something else. I also don't recall that this is how the behavior was by default when creating a View based Android project, but maybe I'm just not remembering correcltly.
Here's the sample Basic Views Activity sample project in Android studio that I've added a couple of edittexts too. Notice the instant dismissal of the IME. Again I'm not doing anything specific with Compose navigation animations, but is this just a default behavior that a Compose NavHost does and a Views NavHost doesn't do? I'm just assuming what I'm talking about is animation related based on your first response.
s
Yep, first screen leaves composition instantly, since you got no animation. Again, it's important to understand that as you navigate somewhere, the screen you were previously on is still alive and all the composables are still on screen and still recompose and everything while the exit animation is playing. In this last video, the focused text field is removed immediately, so the keyboard dismisses. If you want to do the same but without losing focus for some reason, then yeah you gotta just ask the keyboard to dismiss.
plus1 2
b
I see, thanks for the insight. In your opinion do you feel like without keyboard dismissal and the default compose navigation animations, the keyboard staying present during the transition when navigating is jarring, or is it just me that feels that way? I think the main thing is that while compose navigations cause the screen composable to animate, the ime is independent and so it's not like there's any animation that happens on the ime, so it just sits there the whole time until Screen A is fully animated away. I'm mostly just wondering if you've done things to avoid this behavior on your personal apps or if you find it to be fine how my initial video showcased it. Again, I haven't dived into anything fancy with animations on my personal app, these are all just the defaults so that's why I'm not used to the keyboard persistence behavior because by defaults navigating with Views doesn't do this.
s
Yeah I'd dismiss it myself too. In many such scenarios, after "submitting" your input anyway I would dismiss the keyboard anyway, so by the time the submission is done and the navigation would happen, the keyboard is dismissed already.
b
Great, that's what I was thinking but was wondering if it was hacky or seemed normal.
l
If you make your button focusable, and focusable in touch mode, it might work as you click on it.
👀 1
b
That's a good point Louis thanks.
Hey Stylianos, I just realized I have a similar animation issue with other portions of my app. Such as animating from a list element that sets the selected item on the viewModel and calls "popBackStack" even though it looks like the list has been dismissed, it's still not fully animated out and so tapping places on the screen will select another list item and call popBackStack() again, leaving me two screens back potentially. My thoughts for solution are to either disable input of all input for that screen when pressed, or instead to trigger a boolean that is in a launchedEffect for navigating back to the previous screen instead of having the button click directly tied to the "popBackStack()" i.e. the boolean change is only observed once regardless of how many times the button is pressed. Any thoughts?
s
If you do not want to accidentally do two navigation events at the same time, you can wrap your navigation events with a check on the
LocalLifecycleOwner.current.lifecycle
and make sure that it’s RESUMED before you do anything. As soon as you start animating into another screen, it immediately sets the lifecycle to something higher than RESUMED
👀 1
Copy code
@Composable
private fun whenLifecycle(
    state: State,
    lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
    onState: () -> Unit,
): () -> Unit {
    require(state != State.DESTROYED) {
        "Target state is not allowed to be `Lifecycle.State.DESTROYED` because Compose disposes " +
            "of the composition before `Lifecycle.Event.ON_DESTROY` observers are invoked."
    }

    return {
        if (lifecycleOwner.lifecycle.currentState == state) {
            onState()
        }
    }
}
Here is the API https://android-review.googlesource.com/c/platform/frameworks/support/+/2944594/3/lifecycle/[…]main/java/androidx/lifecycle/compose/WhenLifecycle.kt which is gonna be included soon in the compose lifecycle artifact (lifecycle-runtime-compose) itself, but for now you can do it yourself
So tl;dr wrap your onclick that does popBackStack in this lifecycle check and I think you’ll be fine
🙌 1
b
Do you find that when you do things like select items in a list that would typically take the user back to the previous screen that you perform popBackStack() or is it better to do something like this? navigate(inventoryFilterScreenRoute) { popUpTo(inventoryFilterScreenRoute) { inclusive = true } }