java.lang.IllegalStateException:在将 NavBackStackEntry 的 ViewModel 添加到 NavController 的返回堆栈之前,您无法访问它

Nud*_*dge 6 android kotlin android-jetpack-navigation android-jetpack-compose android-jetpack-datastore

我创建了一个composable名为 ResolveAuth. ResolveAuth 是用户在 Splash 后打开应用程序时的第一个屏幕。它所做的只是检查数据存储中是否存在电子邮件。如果是,则重定向到主屏幕,如果不是,则重定向到教程屏幕

这是我的composable代码viewmodel

@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
            }
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

但我不断收到异常消息

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).
Run Code Online (Sandbox Code Playgroud)

我正在使用下面的 jetpack lib 进行导航

 implementation("androidx.navigation:navigation-compose:2.4.0-rc01")
Run Code Online (Sandbox Code Playgroud)

我的主屏幕和教程屏幕没有问题,因为我尝试单独运行它们并且工作正常。

Ric*_*per 1

很容易解决,只需将此when调用添加到 aSide-Effect即可。

LaunchedEffect(Unit){

    while(!isNavStackReady) // Hold execution while the NavStack populates. 
      delay(16) // Keeps the resources free for other threads.

    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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在这里,navigate仅在完全填充后才进行调用currentBackStackEntry,因此不会产生错误。navigate最初的错误发生是因为您在相关可组合项甚至可供导航堆栈使用之前调用。

至于如何更新isNavStackReady变量以反映 navStack 的正确状态,相当简单。在顶级声明中创建变量,以便只有所需的组件才能访问它。如果您愿意,也可以将其放入 viewModel 中。var出于显而易见的原因,将的默认值设置为false。这是更新机制。

@Composable
fun StartDestination(){
  isNavStackReady = true
}
Run Code Online (Sandbox Code Playgroud)

就是这样,真的就是这样。如果您可以成功导航到您在导航图中定义的起始目的地,则意味着 navStack 可能已填充良好。因此,您只需在此处更新此变量,LaunchedEffect那里的块就会响应此更新,并且while一直阻止执行的循环最终将中断。然后它将调用navigate适当的目标路由。但请记住,isNavStackReady要使该机制发挥作用,变量需要是状态持有者,即用 初始化mutableStateOf(false)。当然,使用委托是完全可以的(个人鼓励)。

现在,这一切都很好,但实际上,这并不是正确的实现。你看,这整件事完全由我们的导航 API 在内部处理,但它失败了,因为我们试图完成它的工作,但我们做得很糟糕。

我们正在创建一条中间路线,在应用程序启动时着陆,然后根据计算从那里立即导航到另一个屏幕。因此,我们想要的只是在所需的页面上打开应用程序,即在首次创建应用程序时在所需的页面上启动导航器。我们有一个方便的参数,名为startDestination就是为了这个目的

因此,理想的、简单的、美丽的解决方案就是

startDestination = 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
        }
    }
Run Code Online (Sandbox Code Playgroud)

在你的NavBuilder论点中。最微小、最愚蠢的逻辑缺陷,很多人都无法理解。思考人类思维如何运作是很有趣的......

新年快乐,