如何通过Dagger2注入ViewModel Factory 解决Fragment初始化崩溃问题
错误根因
出现kotlin.UninitializedPropertyAccessException: lateinit property factory has not been initialized崩溃的核心原因如下:
by viewModels传入的工厂lambda会在首次访问signupViewModel实例时执行,如果代码在onAttach方法执行完成(Dagger完成注入)前就访问了signupViewModel,就会调用还未初始化的factory- Dagger Component配置错误:你的AppComponent没有声明针对
SignupFragment的注入方法,或者注入方法的参数是通用Fragment类型而非具体SignupFragment类型,导致inject(this)执行后factory没有被正确赋值
修复方案
方案一:修正注入配置 + 调整ViewModel访问时机
- 确认AppComponent中已声明对应子类的注入方法:
@Singleton @Component interface AppComponent { // 必须是具体的SignupFragment类型,不能用父类Fragment fun inject(fragment: SignupFragment) }
- 不要在Fragment的
init代码块、属性初始化阶段访问signupViewModel,所有ViewModel的调用逻辑放到onViewCreated及之后的生命周期节点执行
方案二:改用自定义懒加载ViewModel(适配所有场景)
如果无法避免提前访问ViewModel的场景,替换by viewModels属性委托为自定义懒加载实现,确保factory注入完成后才会初始化ViewModel:
class SignupFragment : Fragment(R.layout.signup_page) { private val binding by viewBinding(SignupPageBinding::bind) @Inject lateinit var factory: SignupViewModel.Factory // 自定义懒加载,首次访问时才创建ViewModel,此时factory已完成注入 private val signupViewModel: SignupViewModel by lazy { ViewModelProvider(this, factory)[SignupViewModel::class.java] } override fun onAttach(context: Context) { context.appComponent.inject(this) super.onAttach(context) } }
方案三:迁移到Hilt(可选,简化注入逻辑)
如果项目允许迁移到Google官方的Hilt依赖注入框架,可完全规避Factory注入的顺序问题:
- 给ViewModel添加
@HiltViewModel注解,删除自定义Factory类
@HiltViewModel class SignupViewModel @Inject constructor(private val service: AuthService) : ViewModel() { private var email: String = "" private var password: String = "" private val _uiState = MutableStateFlow<SignupState>(SignupState.Success(null)) val uiState: StateFlow<SignupState> = _uiState }
- 给Fragment添加
@AndroidEntryPoint注解,直接用无参的by viewModels获取实例
@AndroidEntryPoint class SignupFragment : Fragment(R.layout.signup_page) { private val binding by viewBinding(SignupPageBinding::bind) private val signupViewModel: SignupViewModel by viewModels() // 无需手动注入Factory,也不需要在onAttach中调用inject方法 }
内容的提问来源于stack exchange,提问作者OhHiMark
相关产品推荐
相关产品推荐

