You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Kotlin DSL构建导航图时navGraphViewModels致进程崩溃

问题描述

在Activity中使用Kotlin DSL初始化导航图,Fragment的onCreate()方法中通过navGraphViewModels()获取ViewModel实例时,进程死亡后重启应用会崩溃,错误信息如下:

Caused by: java.lang.IllegalArgumentException: No destination with route main_navigation is on the NavController's back stack. The current destination is null

相关代码

MainActivity.kt

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    val navController = (supportFragmentManager
        .findFragmentById(R.id.nav_host_fragment) as NavHostFragment).navController
    
    val graph = navController.createGraph(
        startDestination = "MyFragment1", route = "main_navigation"
    ) {
        fragment<MyFragment1>("MyFragment1") {
            deepLink("fi://fragment1")
            argument("testing") {
                type = NavType.StringType
                defaultValue = "hello"
            }
        }

        navigation("MyFragment2", "included_graph") {
            fragment<MyFragment2>("MyFragment2") {
                deepLink("fi://fragment2")
                argument("testing") {
                    type = NavType.StringType
                    nullable = true
                }
            }
        }
    }

    navController.graph = graph
}

MyFragment1.kt

private val vm: Vm by navGraphViewModels("main_navigation")

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    vm.someFunction()
}

完整堆栈信息

Caused by: java.lang.IllegalArgumentException: No destination with route main_navigation is on the NavController's back stack. The current destination is null
    at androidx.navigation.NavController.getBackStackEntry(NavController.kt:2472)
    at com.example.testingnavigation.MyFragment1$special$$inlined$navGraphViewModels$default$1.invoke(NavGraphViewModelLazy.kt:209)
    at com.example.testingnavigation.MyFragment1$special$$inlined$navGraphViewModels$default$1.invoke(NavGraphViewModelLazy.kt:208)
    at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74)
    at androidx.navigation.NavGraphViewModelLazyKt.navGraphViewModels$lambda-3(NavGraphViewModelLazy.kt:208)
    at androidx.navigation.NavGraphViewModelLazyKt.access$navGraphViewModels$lambda-3(NavGraphViewModelLazy.kt:1)
    at com.example.testingnavigation.MyFragment1$special$$inlined$navGraphViewModels$default$4.invoke(NavGraphViewModelLazy.kt:217)
    at com.example.testingnavigation.MyFragment1$special$$inlined$navGraphViewModels$default$4.invoke(NavGraphViewModelLazy.kt:214)
    at androidx.lifecycle.ViewModelLazy.getValue(ViewModelLazy.kt:47)
    at androidx.lifecycle.ViewModelLazy.getValue(ViewModelLazy.kt:35)
    at com.example.testingnavigation.MyFragment1.getVm(MyFragment1.kt:26)
    at com.example.testingnavigation.MyFragment1.onCreate(MyFragment1.kt:30)
    at androidx.fragment.app.Fragment.performCreate(Fragment.java:3094)
    at androidx.fragment.app.FragmentStateManager.create(FragmentStateManager.java:504)
    at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:268)
    at androidx.fragment.app.FragmentStore.moveToExpectedState(FragmentStore.java:122)
    at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1455)
    at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:3034)
    at androidx.fragment.app.FragmentManager.dispatchCreate(FragmentManager.java:2941)
    at androidx.fragment.app.Fragment.restoreChildFragmentState(Fragment.java:1989)
    at androidx.fragment.app.Fragment.onCreate(Fragment.java:1967)
    at androidx.navigation.fragment.NavHostFragment.onCreate(NavHostFragment.kt:163)
    at androidx.fragment.app.Fragment.performCreate(Fragment.java:3094)
    at androidx.fragment.app.FragmentStateManager.create(FragmentStateManager.java:504)
    at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:268)
    at androidx.fragment.app.FragmentStore.moveToExpectedState(FragmentStore.java:114)
    at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1455)
    at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:3034)
    at androidx.fragment.app.FragmentManager.dispatchCreate(FragmentManager.java:2941)
    at androidx.fragment.app.FragmentController.dispatchCreate(FragmentController.java:252)
    at androidx.fragment.app.FragmentActivity.onCreate(FragmentActivity.java:219)
    at com.example.testingnavigation.MainActivity.onCreate(MainActivity.kt:17)
    at android.app.Activity.performCreate(Activity.java:8119)
    at android.app.Activity.performCreate(Activity.java:8103)
    at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1359)
    at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3713)
    ... 11 more

崩溃复现步骤

  • 打开应用并将其置于后台
  • 执行命令:adb shell am kill <package_name>
  • 从应用图标重新启动应用

直接在activity_main.xml布局文件中设置导航图则不会崩溃,请问使用Kotlin DSL实现导航的正确方式是什么?


解决方案

问题核心是进程恢复时的时序冲突:系统恢复Activity和Fragment时,Fragment的重建会先于Activity完成导航图的初始化,此时Fragment调用navGraphViewModels("main_navigation")时,NavController的回退栈中还没有对应导航图的条目,因此触发报错。

方法一:自定义NavHostFragment提前初始化导航图

不要在Activity的onCreate()中动态设置导航图,而是通过自定义NavHostFragment,在其自身的onCreate()方法中用DSL创建导航图,确保导航图在Fragment重建前完成初始化:

class DslNavHostFragment : NavHostFragment() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val navController = navController
        val graph = navController.createGraph(
            startDestination = "MyFragment1", route = "main_navigation"
        ) {
            fragment<MyFragment1>("MyFragment1") {
                deepLink("fi://fragment1")
                argument("testing") {
                    type = NavType.StringType
                    defaultValue = "hello"
                }
            }

            navigation("MyFragment2", "included_graph") {
                fragment<MyFragment2>("MyFragment2") {
                    deepLink("fi://fragment2")
                    argument("testing") {
                        type = NavType.StringType
                        nullable = true
                    }
                }
            }
        }
        navController.graph = graph
    }
}

随后在activity_main.xml中使用这个自定义NavHostFragment:

<androidx.fragment.app.FragmentContainerView
    android:id="@+id/nav_host_fragment"
    android:name="com.example.testingnavigation.DslNavHostFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

方法二:延迟ViewModel的获取时机

如果不想自定义NavHostFragment,可以将ViewModel的获取延迟到Fragment的onViewCreated()生命周期方法中,此时Activity已经完成导航图的设置,NavController状态也已恢复:

private lateinit var vm: Vm

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    vm = navGraphViewModels("main_navigation").value
    vm.someFunction()
}

注意:这种方法需要处理ViewModel初始化延迟的空安全问题,不如第一种方案优雅。

补充说明

直接在布局中设置导航图不会崩溃的原因是:系统恢复NavHostFragment时,会直接从布局资源中读取导航图并完成初始化,Fragment重建时导航图已经存在于NavController中。使用DSL动态设置时,必须保证导航图的初始化时机早于Fragment的onCreate()。

内容的提问来源于stack exchange,提问作者Saran Sankaran

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 17:05:02