为什么在viewModelScope外部获取不可变LiveData的值会返回null
问题根因
该现象本质是协程异步执行的时序问题,和LiveData的可变性无关:
viewModelScope.launch { }包裹的代码是异步执行逻辑,不会阻塞当前线程。调用login方法时,系统会先跳过launch代码块,直接执行后续的loginResponseOutsideOfTheViewModelScope(),此时网络请求还未返回,_loginResponse未赋值,所以读取结果为null- 网络请求完成后,launch内部的赋值逻辑才会执行,因此launch内部打印时可以拿到正常值
- 第二次调用
login方法时,_loginResponse已经被上次请求的结果赋值,所以外部读取时可以拿到缓存的非空值
修复方案
方案1:将后续逻辑移入协程块内部
在请求完成、LiveData赋值后再执行对应逻辑:
fun login(post: LoginBody) { viewModelScope.launch { try { _loginResponse.value = mainRepository.loginPost(post = post) Log.d(LOGCAT,"Login response : ${loginResponse.value?.data?.answerCount}") // 赋值完成后再调用后续逻辑 loginResponseOutsideOfTheViewModelScope() } catch (e: Exception) { Log.d(LOGCAT, "Login error: $e") } } }
方案2:在Fragment中观察LiveData变化(官方推荐)
符合响应式编程规范,不需要主动读取LiveData的值,收到变更回调后直接处理逻辑即可,在Fragment的onViewCreated中添加观察代码:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // 观察登录结果变更 viewModel.loginResponse.observe(viewLifecycleOwner) { loginResponse -> // 此处loginResponse为返回的请求结果,可直接处理业务逻辑 Log.d("DEBUG", "登录结果answerCount:${loginResponse.data?.answerCount}") } val myPost = LoginBody( deviceId = Settings.Secure.getString( requireContext().contentResolver, Settings.Secure.ANDROID_ID ), country = Locale.getDefault().country, language = Locale.getDefault().language, platform = Build.VERSION.SDK_INT.toString(), version = BuildConfig.VERSION_NAME, deviceModel = Build.MODEL ) viewModel.login(myPost) }
内容的提问来源于stack exchange,提问作者oguzata
相关产品推荐
相关产品推荐

