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)
根因分析
代码执行顺序存在逻辑冲突:
- MainActivity的
onCreate方法中,先调用setContentView(R.layout.activity_main)加载布局,而布局中包含NavHostFragment。 - 布局加载时,FragmentManager会立即初始化
NavHostFragment,并自动导航到其默认目的地(即BreakingNewsFragment),触发该Fragment的onViewCreated方法。 - 此时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:
- 先在布局中移除
NavHostFragment的<fragment>标签,改为空容器(比如FrameLayout),id设为nav_host_container。 - 修改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

