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)
 }
Другие вопросы по тегам