为什么 LiveData 不从协程更新?
Why LiveData is not updating from coroutine?
我有功能,达到 API 并将收到的对象放入数据库。然后这个函数 returns Long
作为插入对象的 id。我想 运行 在带有协程的后台线程中使用此函数,并且仍然获得新插入对象的 ID。
但是在我 运行 片段中的函数之后,LiveData 保持为空并且仅在第二次单击按钮时获得正确的 ID。
这是因为我无法从 coroutin 获取任何信息,还是只是请求和数据库插入需要时间,我必须等待“urlEntityId”更新?
这是片段中的代码:
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)
}}
启动协程是异步的。 launch
块中的代码作为协程排队等待 运行,但可能会先到达 launch
块之后的代码。
在协程有机会 运行 之前,您正在获取 LiveData 的值。
您应该很少在片段中使用 LiveData 的 value
属性。 LiveData 的要点是您可以观察它并在 它发生变化后做出反应。如果您只是等待数据准备好并使用它,您的主线程将被锁定以等待数据被获取并冻结 UI.
因此,在您的 ViewModel 函数中,您需要将 isLoading
错误调用移到协程中,以便它在数据准备好之前不会停止显示加载状态:
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 的观察者中调用这个清除函数。
我有功能,达到 API 并将收到的对象放入数据库。然后这个函数 returns Long
作为插入对象的 id。我想 运行 在带有协程的后台线程中使用此函数,并且仍然获得新插入对象的 ID。
但是在我 运行 片段中的函数之后,LiveData 保持为空并且仅在第二次单击按钮时获得正确的 ID。
这是因为我无法从 coroutin 获取任何信息,还是只是请求和数据库插入需要时间,我必须等待“urlEntityId”更新?
这是片段中的代码:
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)
}}
启动协程是异步的。 launch
块中的代码作为协程排队等待 运行,但可能会先到达 launch
块之后的代码。
在协程有机会 运行 之前,您正在获取 LiveData 的值。
您应该很少在片段中使用 LiveData 的 value
属性。 LiveData 的要点是您可以观察它并在 它发生变化后做出反应。如果您只是等待数据准备好并使用它,您的主线程将被锁定以等待数据被获取并冻结 UI.
因此,在您的 ViewModel 函数中,您需要将 isLoading
错误调用移到协程中,以便它在数据准备好之前不会停止显示加载状态:
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 的观察者中调用这个清除函数。