I have created a composable
called ResolveAuth. ResolveAuth is the first screen when user opens the app after Splash. All it does is check whether an email is present in Datastore or not. If yes redirect to main screen and if not then redirect to tutorial screen
Here is my composable
and viewmodel
code
@Composable
fun ResolveAuth(resolveAuthViewModel: ResolveAuthViewModel, navController: NavController) {
Scaffold(content = {
ProgressBar()
when {
resolveAuthViewModel.userEmail.value != "" -> {
navController.navigate(Screen.Main.route) {
popUpTo(0)
}
resolveAuthViewModel.userEmail.value = null
}
resolveAuthViewModel.userEmail.value == "" -> {
navController.navigate(Screen.Tutorial.route) {
popUpTo(0)
}
resolveAuthViewModel.userEmail.value = null
}
}
})
}
@HiltViewModel
class ResolveAuthViewModel @Inject constructor(
private val dataStoreManager: DataStoreManager): ViewModel(){
val userEmail = MutableLiveData<String>()
init {
viewModelScope.launch{
val job = async {dataStoreManager.email.first()}
val email = job.await()
if(email != ""){
userEmail.value = email
}
}
}
}
But I keep getting an exception saying
java.lang.IllegalStateException: You cannot access the NavBackStackEntry's ViewModels until it is added to the NavController's back stack (i.e., the Lifecycle of the NavBackStackEntry reaches the CREATED state).
I am using below jetpack lib for navigation
implementation("androidx.navigation:navigation-compose:2.4.0-rc01")
There is no issue in my Main and Tutorial screen as I tried to run them separately and it works fine.
Easily resolvable, just add this
when
call to aSide-Effect
instead.Here, the call to
navigate
is made only after thecurrentBackStackEntry
has been completely filled, so it yields no error. The original error occurred since you were callingnavigate
before the concerned composable was even made available to the nav stack.As for how to update the
isNavStackReady
variable to reflect the correct state of the navStack, it is fairly simple. Create the variable at a top-level declaration, such that only the required components may access it. May as well throw it inside a viewModel if you please. Set the default value of thevar
tofalse
, for obvious reasons. Here's the update mechanism.That's it, that's really it. If you could successfully navigate to your start destination that you define in the nav graph, it means the navStack has likely been populated well. Hence, you just update this variable here, and the
LaunchedEffect
block up there will respond to this update, and thewhile
loop that's been holding execution off, will finally break. It will then call thenavigate
on the appropriate destination route. Remember, however, that theisNavStackReady
variable, for this mechanism to work, needs to be a state-holder, i.e., initialised withmutableStateOf(false)
. Using delegates, of course, is completely fine (personally encouraged).Now, all this is fine, but actually, it's not quite the right implementation. You see, this entire thing is taken care of completely internally by the navigation APIs for us, but it breaks because we are trying to do its job, and we suck at it.
We are creating an intermediate route to land on, at the start of the app, and from there, immediately navigating to another screen based on calculations. So, all we want is to open the app at a desired page, that is, start the navigator on a desired page when it is first created. We have a handy parameter called
startDestination
, just for that.Hence, the ideal, simple, beautiful solution would be to just
in your
NavBuilder
's arguments. Tiniest silliest logical flaw, that so many people couldn't get. It's intriguing to think how the human mind works...Happy New Year,