kotlin MutableLiveData:无法从协程调用后台线程上的setValue

cotxawn7  于 2023-04-07  发布在  Kotlin
关注(0)|答案(9)|浏览(357)

我正在尝试从协程触发LiveData的更新:

object AddressList: MutableLiveData<List<Address>>()
fun getAddressesLiveData(): LiveData<List<Address>> {
    AddressList.value = listOf()
    GlobalScope.launch {
        AddressList.value = getAddressList()
    }
    return AddressList
}

但我得到以下错误:
IllegalStateException:无法在后台线程上调用setValue
有没有一种方法可以让它与协程一起工作?

p1tboqfb

p1tboqfb1#

使用liveData.postValue(value)代替liveData.value = value。它称为异步。
来自文件:

postValue-将任务发布到主线程,设置给定值。

t5zmwmid

t5zmwmid2#

您可以执行以下操作之一:

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
}
cygmwpex

cygmwpex3#

我刚刚发现使用withContext(Dispatchers.Main){}是可能的:

object AddressList: MutableLiveData<List<Address>>()
fun getAddressesLiveData(): LiveData<List<Address>> {
    GlobalScope.launch {
        withContext(Dispatchers.Main){ AddressList.value = getAddressList() }
    }
    return AddressList
}
zpf6vheq

zpf6vheq4#

尽管其他人指出,在这种情况下,库提供了自己的方法来将操作发布到主线程,但协程提供了一种通用的解决方案,无论给定库的功能如何。
第一步是停止对后台作业使用GlobalScope,这样做将导致泄漏,您的Activity或计划作业或您从中调用的任何工作单元可能会被破坏,但您的作业将继续在后台运行,甚至将其结果提交给主线程。以下是official documentation on GlobalScope的声明:
应用程序代码通常应该使用应用程序定义的CoroutineScope,在GlobalScope示例上使用async或launch是非常不鼓励的。
您应该定义自己的协程作用域,其coroutineContext属性应该包含Dispatchers.Main作为分派器。此外,在函数调用中启动作业并返回LiveData(基本上是另一种Future)的整个模式并不是使用协程的最方便方式。相反,您应该具有

suspend fun getAddresses() = withContext(Dispatchers.Default) { getAddressList() }

在调用站点,您应该launch一个协程,在其中您现在可以自由地调用getAddresses(),就好像它是一个阻塞方法一样,并直接获取地址作为返回值。

velaa5lx

velaa5lx5#

如果你想使用协程来更新UI,有两种方法可以实现

GlobalScope.launch(Dispatchers.Main):

GlobalScope.launch(Dispatchers.Main) {
    delay(1000)     // 1 sec delay
    // call to UI thread
}

如果你想在后台完成一些工作,但之后你想更新UI,这可以通过以下方式实现:

withContext(Dispatchers.Main)

GlobalScope.launch {
    delay(1000)     // 1 sec delay

    // do some background task

    withContext(Dispatchers.Main) {
            // call to UI thread
    }
}
nzrxty8p

nzrxty8p6#

在我的例子中,我不得不将Dispatchers.Main添加到启动参数中,它工作得很好:

val job = GlobalScope.launch(Dispatchers.Main) {
                    delay(1500)
                    search(query)
                }
x6h2sr28

x6h2sr287#

当我在runBlockingTest中调用一个测试用例的协程时,我就遇到了这个错误

TestCoroutineRule().runBlockingTest {

}

我通过添加instantExecutorRule作为类成员来修复它

@get:Rule
var instantExecutorRule = InstantTaskExecutorRule()
brgchamk

brgchamk8#

下面的代码为我从线程更新livedata工作:

val adresses = getAddressList()
 GlobalScope.launch {
     messages.postValue(adresses)
 }
hpcdzsge

hpcdzsge9#

来自@Amir Hossein Ghasemi的答案很好。只是为了补充它。

liveData.value = value用于只在主线程上更新一个LiveData的值。通常用于RxJava

而**liveData.postValue(value)**则可以从任何线程调用,包括主线程和后台线程(异步)

相关问题