Kotlin搭建MVVM+Navigation+Firebase Auth应用运行报FragmentContainerView inflate错误
根本原因定位
你看到的Error inflating class androidx.fragment.app.FragmentContainerView是上层抛出的包装异常,实际底层是代码中多处空指针异常,导致Fragment初始化失败,系统加载布局时抛出了包装后的inflate错误。你移除观察代码后不会触发空指针调用,所以能正常运行。具体问题和修复方案如下:
问题1:AppRepo中所有LiveData未初始化直接赋值为null
你的AppRepo里所有LiveData都直接声明为null,后续调用!!访问会直接触发空指针:
// 原错误代码 val userMutableLiveData: MutableLiveData<FirebaseUser>? = null // 其余LiveData也都是null赋值
修复方案:
将所有LiveData改为直接实例化:
class AppRepo(private val application: Application) { val userMutableLiveData = MutableLiveData<FirebaseUser?>() val loggedOutMutableLiveData = MutableLiveData<Boolean>() val usernameMutableLiveData = MutableLiveData<String>() val uriProfilePicMutableLiveData = MutableLiveData<Uri>() private val firebaseAuth = FirebaseAuth.getInstance() init { firebaseAuth.currentUser?.let { userMutableLiveData.postValue(it) } } // 其余方法保留,删除所有不必要的!!判空,因为上面的属性都已初始化非空 }
问题2:ViewModel初始化逻辑错误
你在ViewModel里写的open fun ViewModel(application: Application)是普通成员方法,不会在ViewModel实例化时自动执行,导致appRepo和所有LiveData都是null,后续访问直接空指针。
修复方案:
删除该普通方法,改用init块实现初始化逻辑:
open class AuthViewModel(application: Application) : AndroidViewModel(application) { private val appRepo = AppRepo(application) val userLiveData = appRepo.userMutableLiveData val usernameLiveData = appRepo.usernameMutableLiveData val uriProfilePicLiveData = appRepo.uriProfilePicMutableLiveData @RequiresApi(Build.VERSION_CODES.P) fun login(email: String?, password: String?) { if (email.isNullOrEmpty() || password.isNullOrEmpty()) return appRepo.loginUsers(email, password) } }
注:建议不要将ViewModel直接命名为ViewModel,和SDK自带类重名容易导致导包错误,这里改名为AuthViewModel方便区分
问题3:LoginFragment逻辑错误
- 类被声明为
abstract抽象类,无法被系统实例化 - 观察LiveData的逻辑写在
onCreate生命周期中,此时Fragment视图还未创建,调用requireView()获取NavController会直接触发空指针
修复方案:
// 去掉abstract修饰 class LoginFragment : Fragment() { private lateinit var emailEditText: EditText private lateinit var passwordEditText: EditText private lateinit var loginButton: Button private lateinit var viewModel: AuthViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) viewModel = ViewModelProvider(this)[AuthViewModel::class.java] } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { return inflater.inflate(R.layout.fragment_login, container, false) } // 视图创建完成后再做视图相关操作 @RequiresApi(Build.VERSION_CODES.P) override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) emailEditText = view.findViewById(R.id.username) passwordEditText = view.findViewById(R.id.password) loginButton = view.findViewById(R.id.login) loginButton.setOnClickListener { val email = emailEditText.text.toString().trim() val password = passwordEditText.text.toString().trim() viewModel.login(email, password) } // 观察逻辑移到这里,此时view已经初始化完成 viewModel.userLiveData.observe(viewLifecycleOwner) { firebaseUser -> firebaseUser?.let { Navigation.findNavController(view) .navigate(R.id.action_loginFragment3_to_mainUIFragment) } } } }
注:观察LiveData时优先使用viewLifecycleOwner而不是Activity作为生命周期所有者,避免Fragment销毁后还收到回调导致内存泄露和崩溃
可选优化:MainActivity布局调整
你当前的FragmentContainerView中app:defaultNavHost设为false,无法响应系统返回键的导航回退操作,如果需要该功能可以改成:
app:defaultNavHost="true"
同时删除没有意义的tools:layout_editor_absoluteX和tools:layout_editor_absoluteY属性,tools属性仅在布局预览时生效,运行时无效。
内容的提问来源于stack exchange,提问作者Mohammad Mawed

