简体   繁体   中英

Jetpack compose navigation closes app instead of returning to previous screen

I have implemented the accompanist navigation animation library in my project and have stumbled into two issues. The first issue is that the animations aren't being applied when navigating from one screen to another. The second issue is that the "back" of the system closes the app to the background instead of returning to the previous screen.

Here is the layout of the app starting from the MainActivity.

MainActivity.kt

@ExperimentalAnimationApi
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    private val preDrawListener = ViewTreeObserver.OnPreDrawListener { false }

    override fun onCreate(savedInstanceState: Bundle?) {
        setTheme(R.style.MyHomeTheme)
        super.onCreate(savedInstanceState)

        WindowCompat.setDecorFitsSystemWindows(window, false)

        val content: View = findViewById(android.R.id.content)
        content.viewTreeObserver.addOnPreDrawListener(preDrawListener)

        lifecycleScope.launch {
            setContent {
                val systemUiController = rememberSystemUiController()

                SideEffect {
                    systemUiController.setStatusBarColor(
                        color = Color.Transparent,
                        darkIcons = true
                    )
                    systemUiController.setNavigationBarColor(
                        color = Color(0x40000000),
                        darkIcons = false
                    )
                }
                MyHomeApp(
                    currentRoute = Destinations.Welcome.WELCOME_ROUTE
                )
            }
            unblockDrawing()
        }
    }

    private fun unblockDrawing() {
        val content: View = findViewById(android.R.id.content)
        content.viewTreeObserver.removeOnPreDrawListener(preDrawListener)
        content.viewTreeObserver.addOnPreDrawListener { true }
    }
}

MyHomeApp.kt

@ExperimentalAnimationApi
@Composable
fun MyHomeApp(currentRoute: String) {
    MyHomeTheme {
        ProvideWindowInsets {
            val navController = rememberAnimatedNavController()
            val scaffoldState = rememberScaffoldState()
            val darkTheme = isSystemInDarkTheme()

            val items = listOf(
                HomeTab.Dashboard,
                HomeTab.Details,
                HomeTab.Settings
            )

            val navBackStackEntry by navController.currentBackStackEntryAsState()
            val currentDestination = navBackStackEntry?.destination

            val bottomPaddingModifier = if (currentDestination?.route?.contains("welcome") == true) {
                Modifier
            } else {
                Modifier.navigationBarsPadding()
            }

            Scaffold(
                modifier = Modifier
                    .fillMaxSize()
                    .then(bottomPaddingModifier),
                scaffoldState = scaffoldState,
                bottomBar = {
                    if (currentDestination?.route in items.map { it.route }) {
                        BottomNavigation {
                            items.forEach { screen ->
                                BottomNavigationItem(
                                    label = { Text(screen.title) },
                                    icon = {},
                                    selected = currentDestination?.hierarchy?.any { it.route == screen.route } == true,
                                    onClick = {
                                        navController.navigate(screen.route) {
                                            // Pop up to the start destination of the graph to
                                            // avoid building up a large stack of destinations
                                            // on the back stack as users select items
                                            popUpTo(navController.graph.findStartDestination().id) {
                                                saveState = true
                                            }
                                            // Avoid multiple copies of the same destination when
                                            // reselecting the same item
                                            launchSingleTop = true
                                            // Restore state when reselecting a previously selected item
                                            restoreState = true
                                        }
                                    }
                                )
                            }
                        }
                    }
                }
            ) { innerPadding ->
                MyHomeNavGraph(
                    modifier = Modifier.padding(innerPadding),
                    navController = navController,
                    startDestination = navBackStackEntry?.destination?.route ?: currentRoute
                )
            }
        }
    }
}

sealed class HomeTab(
    val route: String,
    val title: String
) {
    object Dashboard : HomeTab(
        route = Destinations.Home.HOME_DASHBOARD,
        title = "Dashboard"
    )

    object Details : HomeTab(
        route = Destinations.Home.HOME_DETAILS,
        title = "Details"
    )

    object Settings : HomeTab(
        route = Destinations.Home.HOME_SETTINGS,
        title = "Settings"
    )
}

MyHomeNavGraph.kt

@ExperimentalAnimationApi
@Composable
fun MyHomeNavGraph(
    modifier: Modifier = Modifier,
    navController: NavHostController,
    startDestination: String
) {
     val actions = remember(navController) { Actions(navController = navController) }

    AnimatedNavHost(
        modifier = modifier,
        navController = navController,
        startDestination = startDestination
    ) {
        composable(
            route = Destinations.Welcome.WELCOME_ROUTE,
            enterTransition = {
                when (initialState.destination.route) {
                    Destinations.Welcome.WELCOME_LOGIN_ROUTE ->
                        slideIntoContainer(towards = AnimatedContentScope.SlideDirection.Left, animationSpec = tween(700))
                    else -> null
                }
            },
            exitTransition = {
                when (targetState.destination.route) {
                    Destinations.Welcome.WELCOME_LOGIN_ROUTE ->
                        slideOutOfContainer(towards = AnimatedContentScope.SlideDirection.Left, animationSpec = tween(700))
                    else -> null
                }
            },
            popEnterTransition = {
                when (initialState.destination.route) {
                    Destinations.Welcome.WELCOME_LOGIN_ROUTE ->
                        slideIntoContainer(towards = AnimatedContentScope.SlideDirection.Right, animationSpec = tween(700))
                    else -> null
                }
            },
            popExitTransition = {
                when (targetState.destination.route) {
                    Destinations.Welcome.WELCOME_LOGIN_ROUTE ->
                        slideOutOfContainer(towards = AnimatedContentScope.SlideDirection.Right, animationSpec = tween(700))
                    else -> null
                }
            }
        ) {
            WelcomeScreen(
                navigateToLogin = actions.navigateToWelcomeLogin,
                navigateToRegister = actions.navigateToWelcomeRegister,
            )
        }
        composable(
            route = Destinations.Welcome.WELCOME_LOGIN_ROUTE,
            enterTransition = {
                when (initialState.destination.route) {
                    Destinations.Welcome.WELCOME_ROUTE ->
                        slideIntoContainer(towards = AnimatedContentScope.SlideDirection.Left, animationSpec = tween(700))
                    else -> null
                }
            },
            exitTransition = {
                when (targetState.destination.route) {
                    Destinations.Welcome.WELCOME_ROUTE ->
                        slideOutOfContainer(towards = AnimatedContentScope.SlideDirection.Left, animationSpec = tween(700))
                    else -> null
                }
            },
            popEnterTransition = {
                when (initialState.destination.route) {
                    Destinations.Welcome.WELCOME_ROUTE ->
                        slideIntoContainer(towards = AnimatedContentScope.SlideDirection.Right, animationSpec = tween(700))
                    else -> null
                }
            },
            popExitTransition = {
                when (targetState.destination.route) {
                    Destinations.Welcome.WELCOME_ROUTE ->
                        slideOutOfContainer(towards = AnimatedContentScope.SlideDirection.Right, animationSpec = tween(700))
                    else -> null
                }
            }
        ) {
            WelcomeLoginScreen(
                // Arguments will be passed to navigate to the home screen or other
            )
        }
    }
}

class Actions(val navController: NavHostController) {
    // Welcome
    val navigateToWelcome = {
        navController.navigate(Destinations.Welcome.WELCOME_ROUTE)
    }
    val navigateToWelcomeLogin = {
        navController.navigate(Destinations.Welcome.WELCOME_LOGIN_ROUTE)
    }
}

For simplicity's sake, you can assume that the screens are juste a box with a button in the middle which executes the navigation when they are clicked.

The accompanist version I am using is 0.24.1-alpha (the latest as of this question) and I am using compose version 1.2.0-alpha02 and kotlin 1.6.10.

In terms of animation, the only difference I can see with the accompanist samples is that I don't pass the navController to the screens but I don't see how that could be an issue.

And in terms of using the system back which should return to a previous, I'm genuinely stuck in terms of what could cause the navigation to close the app instead of going back. On other projects, the system back works just fine but not with this one. Is the use of the accompanist navigation incompatible? I'm not sure.

Any help is appreciated!

I found the source of the issue.

The fact that I was setting the startDestination parameter to navBackStackEntry?.destination?.route?: currentRoute meant that each change to the navBackStackEntry recomposed the MyHomeNavGraph and hence the backstack was reset upon the recomposition.

Note to self, watch out when copying navigation from multiple sources!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM