Is there any way to know when a navigation compose...
# compose
d
Is there any way to know when a navigation compose transition has finished. I want to unhide the splash screen and not see the transition. I tried doing this but it doesn't work:
Copy code
LaunchedEffect(viewModel.destination) {
        // TODO: Figure out how to hide the initial transition animation
        if (viewModel.destination == null) return@LaunchedEffect

        navController.navigatePopUpTo(viewModel.destination!!)
        val entry = navController.getBackStackEntry(viewModel.destination!!)
        snapshotFlow { entry.lifecycle.currentState }
            .filter { it == Lifecycle.State.RESUMED }
            .first()

        onUnhideContent()
    }
s
I'm assuming you have a limited number of screens you want to perform this operation on (for example deciding whether the user has been logged in and showing the correct screen accordingly). In this case, you could probably delegate the task of unhiding the splash screen onto these limited number of first-destinations. For example, let's assume I have these two possible first-screens:
Copy code
@Composable
fun MyApp() {
  val isLoggedIn = remember { mutableStateOf<Boolean?>(null) }
  
  val navController = rememberNavController()

  NavHost(
    navController = navController,
    startRoute = if (isLoggedIn == null) "loading"
    else if (isLoggedIn == true) "dashboard"
    else "login",
  ) {
    composable("loading") {
      CircularProgressIndicator()
    }

    composable("dashboard") {
      DashboardScreen {
        // Hide splash screen here
      }
    }

    composable("login") {
      LoginScreen {
        // Hide splash screen here
      }      
    }
  }

@Composable
fun LoginScreen(onRemoveSplash: () -> Unit) {
  LaunchedEffect(Unit) {
    onRemoveSplash()
  }
}

@Composable
fun DashboardScreen(onRemoveSplash: () -> Unit) {
  LaunchedEffect(Unit) {
    onRemoveSplash()
  }
}
t
You can observe the navigation.controller and collect as state
d
@Shubham Singh thanks, the problem is that I have around 5 screens that could be the initial screens, but if I dont find a better solution Ill do what you suggested
@Tomislav Mladenov what do you mean exactly? how will that indicate when the transition finished?
j
Are you targeting Android only?
d
yeah
j
If so, why are you trying to unhide the splash screen? Why not let the system handle showing your splash screen appropriately using the SplashScreen API?
Your splash screen should be the initial view when you app loads and the system will clear it once the first frame from your app is ready to display.
d
Well my startDestination is the home screen, but when the app launches, it checks if the user is logged in and if not then it navigates to the log in screen. If I unhide the splash screen on first frame, the home screen will flash and the transition to the login screen will be visible.
I am using the splash screen API
Copy code
override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        var unhideContent = false
        installSplashScreen().apply {
            setKeepOnScreenCondition {
                !unhideContent
            }
        }

        setContent {
            App(
                onUnhideContent = { unhideContent = true }
            )
        }
    }
j
You should hold the splash screen until you’ve successfully loaded user state.
I have a similiar structure as you where my home screen is my start destination and I want to navigate the user to the login/sign-up screen if they haven’t authenticated or indicated they don’t want to do so. I have a vm for my MainActivity and I collect a holdSplashScreen flow like so:
Copy code
var holdSplash: Boolean by mutableStateOf(true)

// Update the uiState
lifecycleScope.launch {
    lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
        mainViewModel.holdSplashOnScreen
            .onEach { holdSplash = it }
            .collect()
    }
}

// Keep the splash screen on-screen until the UI state is loaded. This condition is
// evaluated each time the app needs to be redrawn so it should be fast to avoid blocking
// the UI.
splashScreen.setKeepOnScreenCondition { holdSplash }
Inside my VM the flow is a
combine
of a few user state related flows. I add an artificial delay in the flow emission of 67 milliseconds to make sure the splash screen always stays on screen long enough to avoid the transition from
Home
to
Login/Signup
d
Thank you, so seems like adding the delay hides the animation and it is small enough that I'm not sacrificing launch time
j
I will say I noticed the delays are something that needs playing with to get just right. On lower end phones, I noticed that using too short of a delay would show the very last few frames of the transition from Home to
Home
to
Login/Signup
. At the end of the day it probably won’t be a big deals because you user won’t be able to accesses the
Home
screen during the transition.
👍 1