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

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)

问题根源

  1. 非空断言触发空指针:AddEditTodoViewModel中使用savedStateHandle.get<Int>("todoId")!!,当SavedStateHandle未获取到"todoId"参数时返回null,非空断言直接触发崩溃。
  2. 导航参数传递错误:第一个Fragment的ViewModel中,OnTodoClick事件仅传递了todoId字符串作为route,未正确将todoId参数传入导航Bundle。
  3. 可选: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 05:42:02