Md. Mahmudul Hasan Shohag
Published at under Android, Kotlin, Jetpack-compose

Don't copy-paste any solution you find online. Here's why.

Recently I saw this video of Philipp Lackner. Where he shows “Why he Stopped Using popBackStack() to Navigate Back” (The video).

# The problem

If popBackStack() is called multiple times and there is nothing on the back stack, then Jetpack Compose shows a blank screen! Because it mistakenly also removes the root stack.

# The discussion

In the video, Philipp suggested navigationUp() in replace of popBackStack(). But though it looks like navigationUp() solves the issue in the simple app, it may introduce some new problems in complex apps. And popBackStack() is the recommended way for navigating back. For the black screen issue, there is a workaround suggested in the official documentation here.

Let’s talk about navigationUp(). One big problem is, that the method restarts the activity! Here is a sample app to show you the issue: gist

This example indicates that calling navigateUp() multiple times will recreate the Activity.

Here is the Logcat output:

// destination: onCreate called <----
// destination: Destination(0x2ac563dd) route=screen_c
// destination: Destination(0x2ac563db) route=screen_a
// destination: Destination(0x2ac563dc) route=screen_b
// destination: Destination(0x2ac563db) route=screen_a
// destination: x: true, y: true
// destination: onCreate called <----
// destination: Destination(0x2ac563dd) route=screen_c
// destination: yes

Here you can see onCreate is called twice. If I am not wrong it will create more issues if we use navigationUp() instead of popBackStack() all the time.

# The solution

I found 2 solutions. One solution will finish the activity if no more back-stack is left, and another one will only allow to call the popBackStack() after the screen is in RESUMED state.

The extensions are given below:

import android.app.Activity
import android.content.Context
import androidx.lifecycle.Lifecycle
import androidx.navigation.NavController

/**
 * Attempts to pop the controller's back stack.
 * If the back stack is empty, it will finish the activity.
 *
 * @param context Activity context.
 */
fun NavController.popBackStackOrFinish(context: Context) {
    if (!popBackStack()) {
        (context as Activity).finish()
    }
}

/**
 * Attempts to pop the controller's back stack.
 * It will check the current lifecycle and only allow the pop
 * if the current state is RESUMED.
 *
 * See [reference](https://github.com/google/accompanist/issues/1408#issuecomment-1673011548)
 */
fun NavController.popBackStackOrIgnore() {
    if (currentBackStackEntry?.lifecycle?.currentState == Lifecycle.State.RESUMED) {
        popBackStack()
    }
}

We can also use dropUnlessResumed {} like the following. It is related to the popBackStackOrIgnore() solution.

@Composable
fun DropUnlessResumed() {
    Button(
        onClick =
            dropUnlessResumed {
                // Run on clicks only when the lifecycle is at least RESUMED.
                navController.popBackStack()
            },
    ) {
        Text(text = "Go Back")
    }
}

I added all the solutions to my Why Not Compose! app. Check out and run the app from here. You can also try the app from Google Play Store from here (Check the tutorials section from the app).

Enjoy.

Comments Subscribe