为啥 LiveData 没有从协程更新?
Posted
技术标签:
【中文标题】为啥 LiveData 没有从协程更新?【英文标题】:Why LiveData is not updating from coroutine?为什么 LiveData 没有从协程更新? 【发布时间】:2022-01-11 16:18:01 【问题描述】:我有功能,它到达 API 并将接收到的对象放入数据库。然后此函数返回 Long
作为插入对象的 id。我想用协程在后台运行这个函数,仍然得到新插入对象的id。
但是在我在片段中运行函数后 LiveData 保持为空,并且仅在第二个按钮单击时获得正确的 id。
这是因为我无法从 cooutin 获得任何东西,或者只是请求和数据库插入需要时间,我必须等待“urlEntityId”更新?
这是来自 Fragment 的代码:
override fun onViewCreated(view: View, savedInstanceState: Bundle?)
super.onViewCreated(view, savedInstanceState)
binding.etSearch.apply
setOnEditorActionListener v, actionId, event ->
if (actionId == EditorInfo.IME_ACTION_SEARCH)
val pattern = DOMAIN_VALIDATION
val url = this.text.toString()
if (url.matches(pattern))
viewModel.loadUrlModelFromApi(url)
val action =
SearchFragmentDirections.actionSearchFragmentToResultFragment(
viewModel.urlEntityId.value ?: 1L
)
navController?.navigate(action)
else
binding.textLayout.error = "Please enter correct domain name."
true
else
false
doOnTextChanged text, start, before, count ->
binding.textLayout.error = null
还有我的 ViewModel 代码:
class SearchViewModel @Inject constructor(private val repository: UrlRepository) : ViewModel()
private val _isLoading = MutableLiveData<Boolean>()
val isLoading: LiveData<Boolean> = _isLoading
private val _toastError = MutableLiveData<String>()
val toastError: LiveData<String> = _toastError
private val _urlEntity = MutableLiveData<UrlEntity>()
val urlEntity: LiveData<UrlEntity> = _urlEntity
private var _urlEntityId = MutableLiveData<Long>()
var urlEntityId: LiveData<Long> = _urlEntityId
fun loadUrlModelFromApi(domainName: String)
_isLoading.postValue(true)
viewModelScope.launch(Dispatchers.IO)
_urlEntityId.postValue(repository.getUrl(domainName))
_isLoading.postValue(false)
【问题讨论】:
将_isLoading.postValue(false)
放入launch
块中。 launch
立即返回,这就是为什么 _isLoading
值在 repository.getUrl
函数被调用之前更改为 false 的原因。此外,您不能在调用loadUrlModelFromApi
后立即使用_urlEntityId
。它还没有收到价值。您需要观察实时数据,然后进行导航。
是的,它成功了,谢谢!
【参考方案1】:
启动协程是异步的。 launch
块中的代码排队等待作为协程运行,但 launch
块之后的代码可能会首先到达。
在您的协程有机会运行之前,您已经获得了 LiveData 的值。
您应该很少在 Fragment 中使用 LiveData 的 value
属性。 LiveData 的重点是您可以观察它并在它发生变化后做出反应。如果您只是等待数据准备好并使用它,您的主线程将被锁定等待获取数据并冻结 UI。
因此,在您的 ViewModel 函数中,您需要将您的 isLoading
false 调用移动到协程内,以便在数据准备好之前它不会停止显示加载状态:
fun loadUrlModelFromApi(domainName: String)
_isLoading.postValue(true)
viewModelScope.launch(Dispatchers.IO)
_urlEntityId.postValue(repository.getUrl(domainName))
_isLoading.postValue(false)
在你的 Fragment 中,你应该观察 LiveData 而不是立即尝试使用它的value
。
override fun onViewCreated(view: View, savedInstanceState: Bundle?)
super.onViewCreated(view, savedInstanceState)
binding.etSearch.apply
setOnEditorActionListener v, actionId, event ->
if (actionId == EditorInfo.IME_ACTION_SEARCH)
val pattern = DOMAIN_VALIDATION
val url = this.text.toString()
if (url.matches(pattern))
viewModel.loadUrlModelFromApi(url)
else
binding.textLayout.error = "Please enter correct domain name."
true
else
false
doOnTextChanged text, start, before, count ->
binding.textLayout.error = null
viewModel.urlEntityId.observe(this)
val action = SearchFragmentDirections.actionSearchFragmentToResultFragment(
viewModel.urlEntityId.value ?: 1L
)
navController?.navigate(action)
我还认为您需要设置导航,以便该片段在进入结果片段时从后台堆栈中删除。否则,当您退出结果片段时,此片段将立即重新打开搜索结果。或者,您可以向 ViewModel 添加一个函数,通过将 LiveData 设置回 null 来清除最近的搜索结果(您必须使其类型为 null)。在 Fragment 的观察者中调用这个 clear 函数。
【讨论】:
以上是关于为啥 LiveData 没有从协程更新?的主要内容,如果未能解决你的问题,请参考以下文章
一个Bug事件,引发我对 LiveData 与 协程 的思考
Kotlin 用Retrofit+OkHttp+协程+LiveData搭建MVVM(Jetpack)来实现网络请求(网络数据JSON解析)显示在RecyclerView(更新中)
纯kotlin+ViewModel+LiveData+协程MVVM
2021-08-13 解决协程调用LiveData异常后LiveData监听失效BUG
Android - ViewModel、LiveData、Room 和 Retrofit 以及协程放在 kotlin 中