LiveData值不更新求助:ViewModelScope赋值后Fragment获取为null
问题描述
在LoginViewModel的viewModelScope中为MutableLiveData赋值,但在LoginFragment中获取该LiveData的值时始终为null。怀疑是ViewModel实例存在问题,但尝试不使用ViewModelFactory后结果相同。
相关代码如下:
LoginViewModel代码
class LoginViewModel(private val loginDao: LoginDao) : ViewModel() { private val _checkingValue = MutableLiveData<Boolean>() val checkingValue: LiveData<Boolean> = _checkingValue /* Validation login data */ fun checkUserFirstNameAndPassword(firstName: String, password: String): Int { return if (firstName.isEmpty()) Const.FIRST_NAME_IS_EMPTY else { if (password.isEmpty()) { Const.PASSWORD_IS_EMPTY } else { Const.VALID_DATA } } } fun checkingIsUserAccountExist (firstName: String) { viewModelScope.launch { try { _checkingValue.value = loginDao.isUserAccountExist(firstName) }catch (e: Exception) { Log.d("LoginViewModel", "Hello from background thread! Something wrong, try Later!") } } } } class LoginViewModelFactory( private val loginDao: LoginDao ) : ViewModelProvider.Factory { override fun <T : ViewModel> create(modelClass: Class<T>): T { if (modelClass.isAssignableFrom(LoginViewModel::class.java)) { @Suppress("UNCHECKED_CAST") return LoginViewModel(loginDao) as T } throw IllegalArgumentException("Unknown ViewModel class") } }
LoginFragment代码
class LoginFragment : Fragment() { private lateinit var binding: FragmentLoginBinding private val viewModelFactory by lazy { LoginViewModelFactory((activity?.application as SignInApplication).database.getDao()) } private val viewModel: LoginViewModel by lazy { ViewModelProvider(this, viewModelFactory)[LoginViewModel::class.java] } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { val binding = FragmentLoginBinding.inflate(inflater, container, false) return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val btLogin: TextView = view.findViewById(R.id.bt_log_in) var firstName: EditText = view.findViewById(R.id.enter_first_name) var password: EditText = view.findViewById(R.id.password_edit) btLogin.setOnClickListener { val firstName = firstName.text.toString().lowercase() .replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() } .filter { !it.isWhitespace() } val password = password.text.toString().lowercase() .replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() } .filter { !it.isWhitespace() } val loginDataState = viewModel.checkUserFirstNameAndPassword( firstName, password ) Log.d("LoginFragment", "$loginDataState") Log.d("LoginFragment", firstName) Log.d("LoginFragment", password) if (loginDataState == Const.FIRST_NAME_IS_EMPTY) { alertDialog("Enter your first name!") } else if (loginDataState == Const.PASSWORD_IS_EMPTY) { alertDialog("Enter your password") } else { viewModel.checkingIsUserAccountExist(firstName) /* If user account exist in Db isUserAccountExist = true */ val isUserExist = viewModel.checkingValue.value Log.d("LoginFragment", "isUserAccountExist $isUserExist") if (isUserExist == true) { val action = LoginFragmentDirections.actionLoginFragmentToRootFragment() findNavController().navigate(action) } else { alertDialog("User isn't exist, please sign up!") val action = LoginFragmentDirections.actionLoginFragmentToSignInPageFragment() findNavController().navigate(action) Log.d("LoginFragment", "isUserAccountExist 2 option $isUserExist") } } } } /* Launch alert dialog */ private fun alertDialog(state: String) { AlertDialog.Builder(requireContext()) .setMessage(state) .setPositiveButton("Ok", null) .create() .show() } }
问题根源与解决方案
问题根源
不是ViewModel实例的问题,而是异步操作的时序错误:
checkingIsUserAccountExist方法通过viewModelScope.launch启动后台协程执行数据库查询,属于异步操作。- 调用该方法后立刻通过
viewModel.checkingValue.value取值,此时数据库查询尚未完成,_checkingValue仍为初始的null,因此获取结果始终为null。
修复步骤
- 观察LiveData变化而非同步取值:在
onViewCreated中通过observe监听checkingValue的更新,待值变化后再处理导航逻辑。 - 修复Binding初始化问题:原代码中
onCreateView声明了局部binding变量,导致类成员的lateinit var binding未被赋值,后续可直接用binding替代findViewById简化代码。
修改后的LoginFragment代码
class LoginFragment : Fragment() { private lateinit var binding: FragmentLoginBinding private val viewModelFactory by lazy { LoginViewModelFactory((activity?.application as SignInApplication).database.getDao()) } private val viewModel: LoginViewModel by lazy { ViewModelProvider(this, viewModelFactory)[LoginViewModel::class.java] } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { // 修复:将binding赋值给类成员变量 binding = FragmentLoginBinding.inflate(inflater, container, false) return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // 监听checkingValue变化,处理导航逻辑 viewModel.checkingValue.observe(viewLifecycleOwner) { isUserExist -> isUserExist?.let { if (it) { val action = LoginFragmentDirections.actionLoginFragmentToRootFragment() findNavController().navigate(action) } else { alertDialog("User isn't exist, please sign up!") val action = LoginFragmentDirections.actionLoginFragmentToSignInPageFragment() findNavController().navigate(action) Log.d("LoginFragment", "isUserAccountExist 2 option $it") } } } binding.btLogIn.setOnClickListener { val firstName = binding.enterFirstName.text.toString().lowercase() .replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() } .filter { !it.isWhitespace() } val password = binding.passwordEdit.text.toString().lowercase() .replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() } .filter { !it.isWhitespace() } val loginDataState = viewModel.checkUserFirstNameAndPassword(firstName, password) Log.d("LoginFragment", "$loginDataState") Log.d("LoginFragment", firstName) Log.d("LoginFragment", password) when (loginDataState) { Const.FIRST_NAME_IS_EMPTY -> alertDialog("Enter your first name!") Const.PASSWORD_IS_EMPTY -> alertDialog("Enter your password") Const.VALID_DATA -> viewModel.checkingIsUserAccountExist(firstName) } } } /* Launch alert dialog */ private fun alertDialog(state: String) { AlertDialog.Builder(requireContext()) .setMessage(state) .setPositiveButton("Ok", null) .create() .show() } }
额外说明
- 使用
viewLifecycleOwner作为LiveData的观察者宿主,确保Fragment视图销毁时自动移除观察,避免内存泄漏。 - 用binding替代
findViewById,代码更简洁且类型安全。 - 数据库查询完成后
_checkingValue.value会更新,此时观察者收到回调并执行对应导航逻辑。
内容的提问来源于stack exchange,提问作者Jekis Osipov
相关产品推荐
相关产品推荐

