Android:无法从@Hilt ViewModel访问SavedStateHandle的问题求助
解决Fragment ViewModel获取todoId时的NullPointerException崩溃
问题概述
两个Fragment架构下,从第二个Fragment的ViewModel中获取todoId时触发NullPointerException,崩溃日志指向AddEditTodoViewModel.kt第33行,即使用非空断言获取SavedStateHandle参数的代码。
崩溃日志
E/AndroidRuntime: FATAL EXCEPTION: main Process: com.test.todolist, PID: 14933 java.lang.NullPointerException at com.test.todolist.ui.add_edit_todo.AddEditTodoViewModel.<init>(AddEditTodoViewModel.kt:33) at com.test.todolist.util.DaggerTodoApp_HiltComponents_SingletonC$ViewModelCImpl$SwitchingProvider.get(DaggerTodoApp_HiltComponents_SingletonC.java:455) at dagger.hilt.android.internal.lifecycle.HiltViewModelFactory$1.create(HiltViewModelFactory.java:104) at androidx.lifecycle.AbstractSavedStateViewModelFactory.create(AbstractSavedStateViewModelFactory.java:89) at dagger.hilt.android.internal.lifecycle.HiltViewModelFactory.create(HiltViewModelFactory.java:116) at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.kt:187) at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.kt:153) at androidx.lifecycle.ViewModelLazy.getValue(ViewModelLazy.kt:53) at androidx.lifecycle.ViewModelLazy.getValue(ViewModelLazy.kt:35) at com.test.todolist.ui.add_edit_todo.AddEditTodoFragment.getViewModel(AddEditTodoFragment.kt:21) at com.test.todolist.ui.add_edit_todo.AddEditTodoFragment.onCreateView(AddEditTodoFragment.kt:42) at androidx.fragment.app.Fragment.performCreateView(Fragment.java:3104) at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:524) at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:261) at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:1890) at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:1814) at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:1751) at androidx.fragment.app.FragmentManager$5.run(FragmentManager.java:538) at android.os.Handler.handleCallback(Handler.java:942) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loopOnce(Looper.java:240) at android.os.Looper.loop(Looper.java:351) at android.app.ActivityThread.main(ActivityThread.java:8381) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:584) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1013)
问题根源
- 非空断言触发空指针:
AddEditTodoViewModel中使用savedStateHandle.get<Int>("todoId")!!,当SavedStateHandle未获取到"todoId"参数时返回null,非空断言直接触发崩溃。 - 导航参数传递错误:第一个Fragment的ViewModel中,
OnTodoClick事件仅传递了todoId字符串作为route,未正确将todoId参数传入导航Bundle。 - 可选:ViewModel生命周期绑定不合理:第二个Fragment使用
activityViewModels()将ViewModel绑定到Activity生命周期,编辑Todo场景下易导致实例干扰。
修复方案
1. 移除非空断言,处理参数null情况
修改AddEditTodoViewModel的初始化代码,给todoId设置默认值-1,避免空指针:
init { val todoId = savedStateHandle.get<Int>("todoId") ?: -1 if (todoId != -1){ viewModelScope.launch { repository.getTodoById(todoId)?.let { todo -> title = todo.title description = todo.description ?: "" this@AddEditTodoViewModel.todo = todo } } } }
2. 修正导航参数传递逻辑
第一步:更新UiEvent.Navigate结构
确保UiEvent支持传递todoId参数:
sealed class UiEvent { data class ShowSnackBar(val message: String, val action: String? = null) : UiEvent() data class Navigate(val route: String, val todoId: Int? = null) : UiEvent() object PopBackStack : UiEvent() }
第二步:修正第一个FragmentViewModel的导航事件
在TodoListViewModel的OnTodoClick分支,正确传递todoId:
is TodoListEvents.OnTodoClick -> sendUiEvent(UiEvent.Navigate(Routes.ADD_EDIT_TODO, events.todo.id))
3. 可选:配置导航图默认参数
在nav_graph.xml中给addEditTodoFragment添加默认参数,确保无参数传入时也有默认值:
<fragment android:id="@+id/addEditTodoFragment" android:name="com.test.todolist.ui.add_edit_todo.AddEditTodoFragment" android:label="Add/Edit Todo"> <argument android:name="todoId" app:argType="integer" android:defaultValue="-1"/> </fragment>
4. 可选:修正ViewModel生命周期绑定
第二个Fragment改用viewModels()绑定到自身生命周期,避免Activity级别的实例干扰:
private val viewModel by viewModels<AddEditTodoViewModel>()
额外优化建议
- 避免内存泄漏:将Fragment中手动创建的
CoroutineScope替换为viewLifecycleOwner.lifecycleScope,比如第一个Fragment的代码:
// 替换前 CoroutineScope(Dispatchers.Main).launch { viewModel.uiEvent.collect() { event -> // ... } } // 替换后 viewLifecycleOwner.lifecycleScope.launch { viewModel.uiEvent.collect() { event -> // ... } }
- 清理重复依赖:Gradle中重复添加了Room KTX依赖,删除其中一行:
// 删除此行重复依赖 // implementation("androidx.room:room-ktx:$room_version")
内容的提问来源于stack exchange,提问作者Ashif Ali
相关产品推荐
相关产品推荐

