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

时间:2018-11-14 16:06:15

标签: kotlin android-livedata kotlin-coroutines

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

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

但出现以下错误:

  

IllegalStateException:无法在后台线程上调用setValue

有没有办法使其与协同程序一起工作?

7 个答案:

答案 0 :(得分:138)

使用liveData.postValue(value)代替liveData.value = value。这称为异步。

来自documentation

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

答案 1 :(得分:3)

就我而言,我必须在启动参数中添加 Dispatchers.Main ,并且运行良好:

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

答案 2 :(得分:1)

我刚刚发现可以使用df1

withContext(Dispatchers.Main){}

答案 3 :(得分:1)

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

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
}

答案 4 :(得分:1)

尽管其他人指出,在这种情况下,库提供了自己的方法来将操作发布到主线程,协程提供了一种通用的解决方案,无论给定库的功能如何,它都可以工作。

第一步是停止将GlobalScope用于后台作业,这样做会导致泄漏,您的活动或计划的作业或您从中调用此作业的任何工作单元可能会被破坏,但您的作业将在后台继续,甚至将其结果提交到主线程。 official documentation on GlobalScope的内容如下:

  

应用程序代码通常应使用应用程序定义的CoroutineScope,强烈建议不要在GlobalScope实例上使用异步或启动。

您应该定义自己的协程范围,并且其coroutineContext属性应包含Dispatchers.Main作为调度程序。此外,在函数调用中启动作业并返回LiveData(基本上是另一种Future)的整个模式,并不是使用协程的最便捷方法。相反,您应该拥有

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

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

答案 5 :(得分:0)

如果您想使用协程更新用户界面,有两种方法可以实现

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

答案 6 :(得分:0)

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

TestCoroutineRule().runBlockingTest {

}

我通过将 InstantExecutorRule 添加为类成员来修复它

@get:Rule
var instantExecutorRule = InstantTaskExecutorRule()