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

Navigation组件场景下lateinit ViewModel未初始化崩溃求助

排查kotlin.UninitializedPropertyAccessException:ViewModel未初始化问题

问题场景

使用Navigation组件的MainActivity加载多个Fragment时,应用崩溃并抛出kotlin.UninitializedPropertyAccessException: lateinit property viewModel has not been initialized错误。

错误栈信息

Caused by: kotlin.UninitializedPropertyAccessException: lateinit property viewModel has not been initialized
    at com.choudhary.newsappmvvm.ui.MainActivity.getViewModel(MainActivity.kt:18)
    at com.choudhary.newsappmvvm.ui.fragments.BreakingNewsFragment.onViewCreated(BreakingNewsFragment.kt:27)
    at androidx.fragment.app.Fragment.performViewCreated(Fragment.java:2987)
    at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:546)
    at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:282)
    at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:2189)
    at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:2106)
    at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:2002)
    at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:3138)

根因分析

代码执行顺序存在逻辑冲突:

  1. MainActivity的onCreate方法中,先调用setContentView(R.layout.activity_main)加载布局,而布局中包含NavHostFragment。
  2. 布局加载时,FragmentManager会立即初始化NavHostFragment,并自动导航到其默认目的地(即BreakingNewsFragment),触发该Fragment的onViewCreated方法。
  3. 此时MainActivity的viewModel初始化代码(viewModel = ViewModelProvider(...))还未执行,Fragment通过(activity as MainActivity).viewModel访问未初始化的lateinit变量,直接导致崩溃。

核心问题:Fragment的初始化流程早于Activity中ViewModel的初始化流程。

解决方案

方案1:修正Fragment获取ViewModel的方式(推荐)

不要直接强转Activity访问ViewModel变量,而是通过ViewModelProvider获取与Activity生命周期绑定的ViewModel实例,确保ViewModel在被访问时已完成初始化:

修改BreakingNewsFragment的onViewCreated方法:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    
    // 创建Repository和Factory(后续可优化为单例避免重复创建)
    val repository = NewsRepository(ArticleDataBase(requireContext()))
    val viewModelFactory = NewsViewModelFactory(repository)
    
    // 通过ViewModelProvider获取与Activity绑定的ViewModel
    viewModel = ViewModelProvider(requireActivity(), viewModelFactory)[NewsViewModel::class.java]
    
    setUpRecylerView()
    // 后续的observe逻辑保持不变
}

这种方式的优势:无论Activity和Fragment的初始化顺序如何,ViewModelProvider都会确保ViewModel实例被正确创建并复用,彻底避免依赖初始化顺序的问题。

方案2:调整MainActivity的初始化顺序

如果希望保持Activity中初始化ViewModel的逻辑,可以延迟NavHostFragment的加载,确保ViewModel初始化完成后再创建Fragment:

  1. 先在布局中移除NavHostFragment的<fragment>标签,改为空容器(比如FrameLayout),id设为nav_host_container。
  2. 修改MainActivity的onCreate方法:
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    
    // 1. 优先初始化ViewModel
    val repository = NewsRepository(ArticleDataBase(this))
    val viewModelFactory = NewsViewModelFactory(repository)
    viewModel = ViewModelProvider(this, viewModelFactory).get(NewsViewModel::class.java)
    
    // 2. 再设置布局
    setContentView(R.layout.activity_main)
    
    // 3. 动态添加NavHostFragment
    val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_container) as NavHostFragment? 
        ?: NavHostFragment.create(R.navigation.nav_graph).also {
            supportFragmentManager.beginTransaction()
                .add(R.id.nav_host_container, it)
                .commit()
        }
    
    bottomNavigationView.setupWithNavController(navHostFragment.findNavController())
}

这样NavHostFragment会在ViewModel初始化完成后才被添加,确保Fragment访问ViewModel时变量已被初始化。

补充优化建议

将NewsRepository改为单例模式,避免重复创建实例:

class NewsRepository private constructor(val db: ArticleDataBase) {
    companion object {
        @Volatile
        private var instance: NewsRepository? = null
        
        fun getInstance(context: Context) = instance ?: synchronized(this) {
            instance ?: NewsRepository(ArticleDataBase(context)).also { instance = it }
        }
    }
    
    suspend fun getBreakingNews(countryCode: String, pageNum: Int) =
        RetrofitInstance.api.getBreakingNews(countryCode, pageNumber = pageNum)
}

后续在Activity和Fragment中都可以通过NewsRepository.getInstance(context)获取同一个实例,避免资源浪费。

内容的提问来源于stack exchange,提问作者vikram Choudhary

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 13:54:20