web-dev-qa-db-fra.com

MutableLiveData: impossible d'appeler setValue sur un thread d'arrière-plan à partir de Coroutine

J'essaie de déclencher une mise à jour sur LiveData à partir d'une coroutine:

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

mais j'obtiens l'erreur suivante:

IllegalStateException: impossible d'appeler setValue sur un thread d'arrière-plan

Existe-t-il un moyen de le faire fonctionner avec des coroutines?

17
kike

Utilisez liveData.postValue(value) au lieu de liveData.value = value. Il est appelé asynchrone.

De documentation :

postValue - Publie une tâche sur un thread principal pour définir la valeur donnée.

13

Vous pouvez effectuer l'une des opérations suivantes:

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

return AddressList
}

ou

fun getAddressesLiveData(): LiveData<List<Address>> {
    AddressList.value = listOf()
    GlobalScope.launch {
        val adresses = getAddressList()
        withContext(Dispatchers.Main) {
            AddressList.value = adresses
        }
    }
    return AddressList
}
11
pdegand59

Dans mon cas, j'ai dû ajouter Dispatchers.Main aux arguments de lancement et cela a bien fonctionné:

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

Bien que d'autres aient souligné que, dans ce cas, la bibliothèque fournit sa propre méthode pour publier une opération sur le thread principal, les coroutines fournissent une solution générale qui fonctionne indépendamment de la fonctionnalité d'une bibliothèque donnée.

La première étape consiste à cesser d'utiliser GlobalScope pour les travaux en arrière-plan, car cela entraînerait des fuites où votre activité, ou travail planifié, ou toute unité de travail à partir de laquelle vous invoquez cela, pourrait être détruite, et pourtant votre travail continuer en arrière-plan et même soumettre ses résultats au thread principal. Voici ce que la documentation officielle sur GlobalScope déclare:

Le code d'application doit généralement utiliser CoroutineScope défini par l'application, l'utilisation de l'async ou le lancement sur l'instance de GlobalScope est fortement déconseillé.

Vous devez définir votre propre portée coroutine et sa propriété coroutineContext doit contenir Dispatchers.Main Comme répartiteur. De plus, le schéma complet de lancement de travaux dans un appel de fonction et de renvoi de LiveData (qui est essentiellement un autre type de Future), n'est pas le moyen le plus pratique d'utiliser les coroutines. Au lieu de cela, vous devriez avoir

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

et sur le site d'appel, vous devez launch une coroutine, dans laquelle vous pouvez maintenant appeler librement getAddresses() comme s'il s'agissait d'une méthode de blocage et obtenir les adresses directement comme valeur de retour.

3
Marko Topolnik

Je viens de comprendre que c'est possible en utilisant withContext(Dispatchers.Main){}:

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

Si vous souhaitez mettre à jour l'interface utilisateur en utilisant Coroutines, il existe 2 façons d'y parvenir

GlobalScope.launch (Dispatchers.Main):

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

Et si vous voulez que certains travaux soient effectués en arrière-plan, mais que vous souhaitez ensuite mettre à jour l'interface utilisateur, cela peut être réalisé de la manière suivante:

withContext (Dispatchers.Main)

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

    // do some background task

    withContext(Dispatchers.Main) {
            // call to UI thread
    }
}
1
Waqar UlHaq