【问题标题】: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。它还没有收到价值。您需要观察实时数据,然后进行导航。
  • 是的,它成功了,谢谢!

标签: android kotlin mvvm kotlin-coroutines


【解决方案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 函数。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-03-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多