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?
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.
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
}
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)
}
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.
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
}
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
}
}