Hi all .. I’ve run into an odd issue with Compose ...
# compose
b
Hi all .. I’ve run into an odd issue with Compose Navigation. I’m using a
NavHost
composable with no special handling for transistions (it uses the default enter/exit and pop transistions). What I’m seeing, is that when I navigate in one case, the new destination slides in diagonally from the top left??? I’m not sure where that’s coming from or what is causing it. Here’s a video that shows the issue. You can see the diagonal animation at the 3 second mark. After that, there are a few button clicks that do additional navigations (using the exact same methods) that work correctly (they use the default fading between destinations). Has anyone else seen this weird “diagonal animation” when navigating?
s
Make sure to have your content take up the max size it can. Just put
fillMaxSize()
on the first contained composable inside your
composable() { Box(Modifier.fillMaxSize()) { screen content… } }
☝️ 1
b
I do have
.fillMaxSize()
on all of my “screen” content
s
Otherwise because you are inside AnimatedContent, if you start from 0.dp on the first frame it will the rush to grab the size it wants to take afterwards. And because the default content alignment inside there is TopLeft then it does this
I do have
.fillMaxSize()
on all of my “screen” content
Show some code
b
here’s the (small, demo) app that I made for the video:
s
You do not have fillMaxSize on your MyNavHost (nor on the container Scaffold which would propagate the constraints if you did) Try that
👍 1
b
haha! well …. I did:
Copy code
Scaffold(modifier = Modifier.fillMaxSize()) {
        MyNavHost(appState.navController, onLoginComplete = { appState.isLoggedIn = true }, modifier = Modifier.fillMaxSize().padding(it))
    }
And now that initial view animates in diagonally from the bottom right
s
Not sure what that looks like now, got another video? And can you simplify this all in one simple file and just send the code for that instead of a zip next time? Will make it infinitely easier for us and for yourself to debug this.
b
Screen_recording_20240412_160831.webm
s
Yeap, shrink to one file and post that code. Also check which navigation + compose etc. version you are using, just to be sure it’s not something super old or anything
b
will do. Was using the latest version of navigation (2.7.7). Updated to the latest alpha (2.8.0-alpha06) to see if that changed anything, but no. will post a single file shortly.
here we go … one file:
Copy code
/**
 * MyAppState handles the primary State for the application as a whole
 */
class MyAppState(
    val navController: NavHostController
) {
    /**
     * isLoggedIn keeps track of the user login state.
     *
     * In a real app, this value could change at any time and
     * as a result of many different things (such as a session timing out, clicking a logout button, etc).
     */
    var isLoggedIn by mutableStateOf(false)
}

@Composable
fun rememberAppState(navController: NavHostController = rememberNavController()): MyAppState {
    return remember { MyAppState(navController) }
}


@Composable
fun MyApp(appState: MyAppState = rememberAppState()) {

    /**
     * The user cannot access the home screen unless/until they are logged in.
     * In addition, if/when their login state changes and they are no longer logged in,
     * then we want to leave the home screen and show the login screen.
     */
    LaunchedEffect(appState.isLoggedIn) {
        if (!appState.isLoggedIn) {

            //THIS NAVIGATION IS THE ONE THAT ANIMATES WEIRD

            appState.navController.navigateToLogin()
        }
    }

    Scaffold(modifier = Modifier.fillMaxSize()) {
        NavHost(
            navController = appState.navController,
            startDestination = "home",
            modifier = Modifier
                .fillMaxSize()
                .padding(it)
        ) {

            composable(route = "home") {
                Surface(modifier = Modifier.fillMaxSize()) {
                    Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center) {
                        Text("Welcome Logged in user!!")
                        Spacer(modifier = Modifier.height(24.dp))
                        Button(onClick = { appState.navController.navigateToLogin() }) {
                            Text("Logout")
                        }
                    }
                }                
                
                
                
                
            }

            composable(route = "login") {
                Surface(modifier = Modifier.fillMaxSize()) {
                    Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center) {
                        Text("Click the button Below to Login")
                        Spacer(modifier = Modifier.height(24.dp))
                        Button(onClick = { 
                            appState.isLoggedIn = true
                            appState.navController.navigateToHome()
                        }) {
                            Text("Login")
                        }
                    }
                }
            }
        }

    }
}

/**
 * Navigate to login.
 * Pops all destinations from the backstack, making login
 * the root destination.
 */
fun NavHostController.navigateToLogin() {

    val rootDestination = this.graph.findStartDestination().id

    navigate(
        route = "login",
        navOptions = navOptions {
            popUpTo(rootDestination) { inclusive = true }
        }
    )
}

/**
 * Navigate to home.
 * Pops all destinations from the backstack, making home
 * the root destination.
 */
fun NavHostController.navigateToHome() {

    val rootDestination = this.graph.findStartDestination().id

    navigate(
        route = "home",
        navOptions = navOptions {
            popUpTo(rootDestination) { inclusive = true }
        }
    )
}
s
Oh you do a navigation like on the very first frame before anything is even rendered. This might be making it so that as soon as the app starts, the first destination is tried to be shown and it may be animating in still, but then you navigate already, and then since the first screen hasn’t even settled yet this happens
Try this silly thing for me. Move this LaunchedEffect after the
NavHost()
call and see what that does for you
m
b
@Stylianos Gakis yeah, it’s definitely related to to doing that first navigation at startup when it determines that the user is logged out and needs to navigate to login. Moving the
LaunchedEffect
after the
NavHost
doesn’t change anything. I can put a
delay(xxx)
in the
LaunchedEffect
and that does “fix” it, but that’s not great for obvious reasons. I mainly posted the issue here to get a sense of whether this is expected behavior, or an actual defect (like those that @Mahmoo pointed out). I get that what I’m doing (navigating to a new destination immediately) is maybe a bit unorthodox so maybe I shouldn’t be surprised by the weird animation. At the same time, it seems odd that there is any “sliding” animation of content at all, when the NavHost is defined without any sliding transitions. If it is indeed a defect, I can file it on the issue tracker. If it’s expected behavior, I think I can mitigate it, by dynamically calculating the
startDestination
to pass to the
NavHost
based on the
isLoggedIn
state, and also adding some additional logic to my
navigateToLogin()
extension function so that it doesn’t pop the start destination if that destination is already the login destination.
s
Yeah file an issue. This experience is coming from AnimatedContent as I understand, not something special that NavHost is doing. And yeah don't change the start destination like that, it's most likely gonna have other effects that you don't want here. Btw in a dead simple repro I tried to make, putting the nav after NavHost also fixed the issue for me. Interesting that you don't see the same.
283 Views