Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Navigation Animation] When pressing back twice in succession, the display shows a screen with no comparables #1408

Closed
jeluchu opened this issue Nov 21, 2022 · 4 comments
Assignees

Comments

@jeluchu
Copy link

jeluchu commented Nov 21, 2022

Description
By having 2 screens (or more), but in the case of 2, to be simpler, a Main screen, which will be the initial screen, and the second, a Details screen, when being in the details screen, and pressing the back button twice in a row, the navigation goes through the Main screen, and shortly after it will go to another screen without any content, with a black background

Screenshot_2022-11-20-21-00-11-912_com jeluchu vdelaesquina

Steps to reproduce

  1. Have a minimum of 2 screens, a primary source screen and a secondary destination screen
                AnimatedNavHost(
                    navController = LocalNavHost.current,
                    startDestination = NavItem.Dashboard.route
                ) {
                    composable(NavItem.Dashboard) { PodcastsView() }
                    composable(NavItem.Favourites) { FavouritesView() }
                    composable(NavItem.PodcastDetails) { PodcastDetailView() }
                }

  1. From the main screen, access the secondary screen
  2. From the secondary display double click on the back button, in this case it happens mainly with navController.popBackStack()
  3. After pressing the back button twice in a row on the secondary screen, the app will navigate to the main screen and then navigate to an empty screen

Expected behavior
The expected behaviour is that once in the main screen defined as startDestination, it does not go backwards, or in case of going backwards, it exits directly from the application, but does not show an empty screen.

Additional context
Here are the versions of the Compose-related libraries I am currently using:

composeOptions.kotlinCompilerExtensionVersion = "1.3.2"

dependencies {
    implementation(platform("androidx.compose:compose-bom:2022.11.00"))
    implementation("androidx.compose.ui:ui")
    implementation("androidx.compose.material:material")
    implementation("androidx.compose.ui:ui-tooling-preview")
    debugImplementation("androidx.compose.ui:ui-tooling")
    implementation("androidx.activity:activity-compose:1.6.1")
    implementation("androidx.compose.material:material-icons-extended")
    implementation("androidx.compose.foundation:foundation")
    implementation("androidx.compose.foundation:foundation-layout")
    implementation("androidx.compose.animation:animation")
    implementation("androidx.compose.runtime:runtime")
    implementation("androidx.constraintlayout:constraintlayout-compose:1.0.1")
    implementation("androidx.navigation:navigation-compose:2.5.3")

    implementation("com.google.accompanist:accompanist-systemuicontroller:0.27.0")
    implementation("com.google.accompanist:accompanist-navigation-animation:0.27.1")
    implementation("com.google.accompanist:accompanist-pager:0.27.1")
    implementation("com.google.accompanist:accompanist-pager-indicators:0.27.1")
}
@ianhanniballake
Copy link
Collaborator

For

From the secondary display double click on the back button, in this case it happens mainly with navController.popBackStack()

Are you pressing the system back button? Or do you have a button on the screen that is manually calling navController.popBackStack()? Please include your code.

@jeluchu
Copy link
Author

jeluchu commented Nov 21, 2022

@ianhanniballake I've put the navigation in the previous comment so I'll tell you the rest, apparently I've tried now to press the back button of the system and that way it doesn't seem to reproduce the problem.

On the initial screen I simply have a Scaffold with a LazyVerticalGrid, clicking on one of these items navigates to the second screen. The navigation with the second screen is as follows:

class Destination(navController: NavHostController) {
    val goBack: () -> Unit = { navController.popBackStack() }
    val favourites: () -> Unit = { navController.navigate(NavItem.Favourites.baseRoute) }
    val podcastDetail: (String) -> Unit = { id ->
        navController.navigate(NavItem.PodcastDetails.createNavRoute(id))
    }
}

Where NavItem and NavArgs is as follows:

sealed class NavItem(
    val baseRoute: String,
    private val navArgs: List<NavArgs> = emptyList()
) {
    val route = run {
        val argKeys = navArgs.map { "{${it.key}}" }
        listOf(baseRoute)
            .plus(argKeys)
            .joinToString("/")
    }

    val args = navArgs.map {
        navArgument(it.key) { type = it.navType }
    }

    object Dashboard: NavItem("home")
    object Favourites: NavItem("favourites")
    object PodcastDetails: NavItem("podcastDetails", listOf(NavArgs.PodcastTitle)) {
        fun createNavRoute(podcastTitle: String) = "$baseRoute/$podcastTitle"
    }
}

enum class NavArgs(
    val key: String,
    val navType: NavType<*>
) {
    PodcastTitle(key = "podcastTitle", navType = NavType.StringType)
}

In the second screen I have another Scaffold, and in it, inside the TopAppBar, in the navigationIcon button, I call the navigation to perform goBack, which manually calls navController.popBackStack()

I hope this information will help you a little more, if not, please do not hesitate to ask me again

@ianhanniballake
Copy link
Collaborator

Looks like your click listeners suffer the same problem as #1320, where they are not checking the Lifecycle of the NavBackStackEntry to know if you've already navigated away from that screen - you'd want to have each click listener verify that the screen is RESUMED if you want to ensure that you are actually only calling navigate or popBackStack once.

That alone will always avoid a blank screen, since there would be no way for your navController.popBackStack() to actually be popping anything but the detail screen off of the back stack.

The reason you are getting a blank screen is because you are also not following the documentation:

NavController.popBackStack() returns a boolean indicating whether it successfully popped back to another destination. The most common case when this returns false is when you manually pop the start destination of your graph.

When the method returns false, NavController.getCurrentDestination() returns null. You are responsible for either navigating to a new destination or handling the pop by calling finish() on your Activity

That double tap of the button, without checking the Lifecycle, is not just popping the detail screen, but also popping the screen before that (the NavController is doing exactly what you told it to do: e.g., if you wanted to pop back twice, calling popBackStack() would indeed be a valid way to do that). Once you've popped every screen, there's nothing left to display, hence the blank screen.

The system back button works because it disables itself as soon as there is only one destination left on the back stack, ensuring that the activity takes the next back button press and closes the activity.

@ianhanniballake ianhanniballake closed this as not planned Won't fix, can't repro, duplicate, stale Nov 21, 2022
@gmikhail
Copy link

Based on the above (and #1320), I will summarize that the solution is to check the lifecycle before navigating.

if (navController.currentBackStackEntry?.lifecycle?.currentState == Lifecycle.State.RESUMED) {
    navController.popBackStack()
}

This is a ready-made code snippet for those who come to this topic from Google search.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants