Dagger lateinit属性未初始化:从DialogFragment返回Fragment时崩溃
先理清问题根源
你碰到的UninitializedPropertyAccessException,大概率是DialogFragment持有了UserFragment的旧实例引用导致的。举个常见场景:当发生屏幕旋转、系统语言切换这类配置变化时,系统会销毁原来的UserFragment并重建一个新实例,但之前弹出的DialogFragment还攥着那个已经被销毁的旧Fragment对象。等回调触发时,旧实例里的appDatabase因为实例已销毁根本没机会重新初始化,自然就抛出未初始化的异常。另外还有一种可能:你用强引用让DialogFragment持有UserFragment作为监听器,导致UserFragment无法被正确回收,它处于销毁状态时appDatabase已经被释放,回调时就会出问题。
具体解决方案
1. 用ViewModel共享数据(最推荐的方案)
别直接通过监听器传数据,用ViewModel在Fragment间共享状态才是Jetpack组件的标准玩法,能从根源避开生命周期不一致的坑:
- 先创建一个共享的ViewModel,比如
UserViewModel:
class UserViewModel : ViewModel() { val newUserLiveData = MutableLiveData<User>() fun submitNewUser(user: User) { newUserLiveData.value = user } }
- 在UserFragment和DialogFragment中,通过
activityViewModels()获取同一个和Activity绑定的ViewModel实例:- UserFragment里:
private val userViewModel: UserViewModel by activityViewModels() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // 监听LiveData,收到新用户数据后直接操作数据库 userViewModel.newUserLiveData.observe(viewLifecycleOwner) { newUser -> appDatabase.userDao().insert(newUser) // 这里可以加更新UI的逻辑 } }- DialogFragment里:
private val userViewModel: UserViewModel by activityViewModels() // 用户确认输入后,把数据传给ViewModel即可 private fun onConfirmUserInput() { val newUser = User(...) // 构造你的新用户数据 userViewModel.submitNewUser(newUser) dismiss() }
这种方式完全不用直接持有Fragment引用,ViewModel的生命周期和Activity绑定,不管Fragment怎么重建,数据和引用都不会出问题。
2. 正确处理监听器引用(如果坚持用监听器)
要是你不想用ViewModel,那得确保DialogFragment持有的监听器是当前活跃的UserFragment实例,还要避免内存泄漏:
- 给DialogFragment的监听器套上弱引用:
class AddUserDialogFragment : DialogFragment() { private var listener: WeakReference<OnUserAddedListener>? = null interface OnUserAddedListener { fun onUserAdded(newUser: User) } fun setListener(listener: OnUserAddedListener) { this.listener = WeakReference(listener) } // 确认按钮点击逻辑 private fun onConfirm() { val newUser = User(...) listener?.get()?.let { // 先检查Fragment是否还活跃 if (it is Fragment && !it.isAdded) return@let it.onUserAdded(newUser) } dismiss() } }
- 在UserFragment里,要在合适的生命周期方法里清理和重新设置监听器:
private var addUserDialog: AddUserDialogFragment? = null override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) btnAddUser.setOnClickListener { addUserDialog = AddUserDialogFragment().apply { setListener(this@UserFragment) } addUserDialog?.show(childFragmentManager, "AddUserDialog") } } override fun onDestroyView() { // 销毁前清空监听器引用,避免内存泄漏 addUserDialog?.setListener(null) addUserDialog = null super.onDestroyView() }
另外,回调方法里一定要先检查Fragment状态,确保appDatabase可用:
override fun onUserAdded(newUser: User) { // 先确认Fragment还处于活跃状态 if (!isAdded || view == null) return // 再操作数据库 appDatabase.userDao().insert(newUser) }
3. 检查appDatabase的初始化时机
确保appDatabase是在Fragment的onViewCreated()或者onActivityCreated()里初始化的,别放在构造函数里。因为Fragment构造函数在重建时会被调用,但此时上下文可能还没准备好,而onViewCreated()是Fragment绑定到View之后才调用的,更安全:
private lateinit var appDatabase: AppDatabase override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) appDatabase = AppDatabase.getInstance(requireContext()) }
总结
最省心的还是用ViewModel方案,它彻底解决了Fragment生命周期不一致的问题,代码也更简洁易维护。如果一定要用监听器,记得用弱引用+检查Fragment活跃状态,别去访问已经销毁的实例属性。
内容的提问来源于stack exchange,提问作者Ememobong AkpanEkpo

