Я использую библиотеку OkHttp для загрузки некоторых данных из Интернета в свой androidx.lifecycle.ViewModel Затем я хочу обновить свой LiveData. Кажется, что выполнение этого из фонового потока вызывает исключение следующим образом:

2022-01-17 15:47:59.589 7354-7396/com.example.myapplication E/AndroidRuntime: FATAL EXCEPTION: OkHttp Dispatcher
    Process: com.example.myapplication, PID: 7354
    java.lang.IllegalStateException: Cannot invoke setValue on a background thread
        at androidx.lifecycle.LiveData.assertMainThread(LiveData.java:487)
        at androidx.lifecycle.LiveData.setValue(LiveData.java:306)
        at androidx.lifecycle.MutableLiveData.setValue(MutableLiveData.java:50)
        at com.example.myapplication.MainActivityViewModel$getOneMoreCat$1.invoke(MainActivityViewModel.kt:86)
        at com.example.myapplication.MainActivityViewModel$getOneMoreCat$1.invoke(MainActivityViewModel.kt:39)
        at com.example.myapplication.singleton.CommunicationManager$sendRequest$1.onResponse(CommunicationManager.kt:24)
        at okhttp3.internal.connection.RealCall$AsyncCall.run(RealCall.kt:519)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at java.lang.Thread.run(Thread.java:923)

Теперь я нашел два разных способа отправки в основной поток из ViewModel (который не имеет ссылки на контекст в соответствии с рекомендациями AAC), см. здесь:

            GlobalScope.launch {
                withContext(Dispatchers.Main) {
                    // do whatever, e.g. update LiveData
                }
            }

Или

            Handler(Looper.getMainLooper()).post(Runnable {
                   // do whatever, e.g. update LiveData
            })

Какой правильный путь? То есть наименее эффективен во время выполнения.

Обновление Я обнаружил, что также могу выполнять myLiveData.post(), и это работает из фонового потока.

Тем не менее, я хотел бы знать, как правильно отправить работу в основной поток в современном Android под kotlin.

-1
zaitsman 17 Янв 2022 в 07:53
1
Если его просто установить значение LiveData, вы можете просто использовать postValue. Если есть какая-либо другая операция пользовательского интерфейса, вы можете использовать Dispatchers . Кроме этого, не используйте GlobalScope. См. Это и Это.
 – 
ADM
17 Янв 2022 в 08:21

3 ответа

Внутренняя модель просмотра,

private val _downloading = MutableLiveData<Result<Boolean>>()
val downloading: LiveData<Result<Boolean>>
    get() = _downloading

fun downloadFile() {
    viewModelScope.launch {
        try {
            _downloading.value = Result.Loading
            val result = withContext(Dispatchers.IO) {
                // download something
            }
            _downloading.value = Result.Success(true)
        } catch (ex: Exception) {
            _downloading.value = Result.Failure(ex)
        }
    }
}

В действии/фрагменте

 viewModel.downloading.observe(this, {
        when (it) {
            is Result.Failure -> TODO()
            Result.Loading -> TODO()
            is Result.Success -> TODO()
        }
    })

Result — это закрытый класс для захвата состояния, что, в свою очередь, поможет нам соответствующим образом обновить пользовательский интерфейс. Также viewmodelscope используется вместо GlobalScope, так как мы не хотим, чтобы загрузка продолжалась, когда модель представления уничтожена.

0
Hussain 17 Янв 2022 в 11:04

Правильный способ перенаправить работу из фонового потока в основной поток с помощью LivaData — использовать LivaData.postValue(). Он отправляет задачу в основной поток, чтобы установить заданное значение.

Другой подход заключается в использовании свойства расширения viewModelScope в ViewModel, по умолчанию он использует контекст Dispatchers.Main для выполнения сопрограммы, это означает, что вы можете обновить пользовательский интерфейс в такой сопрограмме. Например, в вашем классе ViewModel:

viewModelScope.launch {
    val result = makeNetworkCall()
    // use result to update UI
    liveData.value = result
}

// withContext - switches context to background thread
suspend fun makeNetworkCall(): String = withContext(Dispatchers.IO) {
    delay(1000) // simulate network call
    "SomeResult"
}

Зависимость для использования viewModelScope:

implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0'

GlobalScope крайне не рекомендуется использовать, его можно использовать только в определенных случаях, вот описание, почему его не использовать.

0
Sergey 17 Янв 2022 в 12:37

Есть много способов сделать это, вы можете просто отправить значение в живые данные, используя диспетчер и обработчик, который работает в основном потоке, поскольку вы предоставляете цикл основного потока.

Другой способ - вы можете использовать функции высокого порядка для обновления моделей просмотра, которые просты в использовании, и попробуйте.

-1
Muhammad Adnan 17 Янв 2022 в 08:54
Функции высокого порядка будут протекать, потому что они будут захватывать контекст, а сеть может сохранять воссоздание действий (например, поворот экрана).
 – 
zaitsman
17 Янв 2022 в 09:02