MutableLiveData: невозможно вызвать setValue в фоновом потоке из Coroutine
Я пытаюсь вызвать обновление LiveData из сопрограммы:
object AddressList: MutableLiveData<List<Address>>()
fun getAddressesLiveData(): LiveData<List<Address>> {
AddressList.value = listOf()
GlobalScope.launch {
AddressList.value = getAddressList()
}
return AddressList
}
но я получаю следующую ошибку:
IllegalStateException: невозможно вызвать setValue в фоновом потоке
Есть ли способ заставить его работать с сопрограммами?
9 ответов
Использовать liveData.postValue(value)
вместо того liveData.value = value
. Это называется асинхронным.
Из документации:
postValue - отправляет задачу в основной поток, чтобы установить заданное значение.
Вы можете сделать одно из следующего:
object AddressList: MutableLiveData<List<Address>>()
fun getAddressesLiveData(): LiveData<List<Address>> {
AddressList.value = listOf()
GlobalScope.launch {
AddressList.postValue(getAddressList())
}
return AddressList
}
или же
fun getAddressesLiveData(): LiveData<List<Address>> {
AddressList.value = listOf()
GlobalScope.launch {
val adresses = getAddressList()
withContext(Dispatchers.Main) {
AddressList.value = adresses
}
}
return AddressList
}
Я только что понял, что это возможно с помощью withContext(Dispatchers.Main){}
:
object AddressList: MutableLiveData<List<Address>>()
fun getAddressesLiveData(): LiveData<List<Address>> {
GlobalScope.launch {
withContext(Dispatchers.Main){ AddressList.value = getAddressList() }
}
return AddressList
}
Хотя другие отмечают, что в этом случае библиотека предоставляет собственный метод для публикации операции в основном потоке, сопрограммы предоставляют общее решение, которое работает независимо от функциональных возможностей данной библиотеки.
Первым шагом является прекращение использования GlobalScope
для фоновых заданий выполнение этого приведет к утечкам, из-за которых ваша деятельность, или запланированная работа, или любая другая единица работы, из которой вы ее вызываете, могут быть разрушены, и все же ваша работа будет продолжаться в фоновом режиме и даже отправлять результаты в основной поток., Вот что официальная документация наGlobalScope
состояния:
Код приложения обычно должен использовать определяемый приложением CoroutineScope, поэтому использование асинхронного или запуска на экземпляре GlobalScope крайне не рекомендуется.
Вы должны определить свой собственный объем сопрограмм и его coroutineContext
свойство должно содержать Dispatchers.Main
как диспетчер. Кроме того, весь шаблон запуска заданий в вызове функции и возврата LiveData
(который в основном другой вид Future
), не самый удобный способ использовать сопрограммы. Вместо этого вы должны иметь
suspend fun getAddresses() = withContext(Dispatchers.Default) { getAddressList() }
и на сайте вызова вы должны launch
сопрограмму, в рамках которой вы теперь можете свободно звонить getAddresses()
как если бы это был метод блокировки и получить адреса непосредственно в качестве возвращаемого значения.
Если вы хотите обновить пользовательский интерфейс с помощью сопрограмм, есть два способа добиться этого.
GlobalScope.launch(Dispatchers.Main):
GlobalScope.launch(Dispatchers.Main) {
delay(1000) // 1 sec delay
// call to UI thread
}
И если вы хотите, чтобы некоторая работа выполнялась в фоновом режиме, но после этого вы хотите обновить пользовательский интерфейс, это может быть достигнуто следующим образом:
withContext(Dispatchers.Main)
GlobalScope.launch {
delay(1000) // 1 sec delay
// do some background task
withContext(Dispatchers.Main) {
// call to UI thread
}
}
В моем случае мне пришлось добавить Dispatchers.Main в аргументы запуска, и все заработало:
val job = GlobalScope.launch(Dispatchers.Main) {
delay(1500)
search(query)
}
Ответ от @Amir Hossein Ghasemi хорош. Просто чтобы добавить к этому.
liveData.value = значение используется для обновления значения LiveData только в основном потоке. Обычно используется для RxJava
В то время как liveData.postValue(value) вы можете вызвать его из любого потока, включая основной поток и фоновые потоки (асинхронные). Обычно используется для сопрограмм
Я столкнулся с этой ошибкой, когда вызвал сопрограмму внутри runBlockingTest для тестового примера.
TestCoroutineRule().runBlockingTest {
}
Я исправил это, добавив InstantExecutorRule в качестве члена класса
@get:Rule
var instantExecutorRule = InstantTaskExecutorRule()
Ниже код работал у меня для обновления живых данных из потока:
val adresses = getAddressList()
GlobalScope.launch {
messages.postValue(adresses)
}