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

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。

修复步骤

  1. 观察LiveData变化而非同步取值:在onViewCreated中通过observe监听checkingValue的更新,待值变化后再处理导航逻辑。
  2. 修复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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 19:25:02