Estoy tratando de activar una actualización en LiveData desde una rutina:
object AddressList: MutableLiveData<List<Address>>() fun getAddressesLiveData(): LiveData<List<Address>> { AddressList.value = listOf() GlobalScope.launch { AddressList.value = getAddressList() } return AddressList }pero me sale el siguiente error:
IllegalStateException: no se puede invocar setValue en un hilo de fondo
¿Hay alguna manera de hacer que funcione con coroutines?
Estaba enfrentando este error cuando llamé a una rutina dentro de runBlockingTest para un caso de prueba
TestCoroutineRule().runBlockingTest { }Lo solucioné agregando instantExecutorRule como miembro de la clase
@get:Rule var instantExecutorRule = InstantTaskExecutorRule()Acabo de darme cuenta de que es posible usando withContext(Dispatchers.Main){} :
object AddressList: MutableLiveData<List<Address>>() fun getAddressesLiveData(): LiveData<List<Address>> { GlobalScope.launch { withContext(Dispatchers.Main){ AddressList.value = getAddressList() } } return AddressList }Puede hacer uno de los siguientes:
object AddressList: MutableLiveData<List<Address>>() fun getAddressesLiveData(): LiveData<List<Address>> { AddressList.value = listOf() GlobalScope.launch { AddressList.postValue(getAddressList()) } return AddressList }o
fun getAddressesLiveData(): LiveData<List<Address>> { AddressList.value = listOf() GlobalScope.launch { val adresses = getAddressList() withContext(Dispatchers.Main) { AddressList.value = adresses } } return AddressList }Aunque otros han señalado que, en este caso, la biblioteca proporciona su propio método para publicar una operación en el subproceso principal, las corrutinas brindan una solución general que funciona independientemente de la funcionalidad de una biblioteca determinada.
El primer paso es dejar de usar GlobalScope para trabajos en segundo plano, ya que esto dará lugar a fugas en las que su actividad, trabajo programado o cualquier unidad de trabajo desde la que invoque esto puede destruirse y, sin embargo, su trabajo continuará en segundo plano y incluso enviar sus resultados al hilo principal. Esto es lo que dice la documentación oficial sobre GlobalScope :
El código de la aplicación generalmente debe usar CoroutineScope definido por la aplicación; se desaconseja enfáticamente usar async o iniciar en la instancia de GlobalScope.
Debe definir su propio alcance coroutine y su propiedad coroutineContext debe contener Dispatchers.Main como el despachador. Además, todo el patrón de iniciar trabajos dentro de una llamada de función y devolver LiveData (que es básicamente otro tipo de Future ), no es la forma más conveniente de usar rutinas. En su lugar, deberías tener
suspend fun getAddresses() = withContext(Dispatchers.Default) { getAddressList() } y en el sitio de la llamada debe launch una corrutina, dentro de la cual ahora puede llamar libremente a getAddresses() como si fuera un método de bloqueo y obtener las direcciones directamente como un valor de retorno.
Si desea actualizar la interfaz de usuario utilizando Coroutines , hay 2 formas de lograrlo
GlobalScope.launch(Dispatchers.Main):
GlobalScope.launch(Dispatchers.Main) { delay(1000) // 1 sec delay // call to UI thread }Y si desea que se realice algún trabajo en segundo plano, pero luego desea actualizar la interfaz de usuario, puede lograrlo de la siguiente manera:
withContext(Dispatchers.Main)
GlobalScope.launch { delay(1000) // 1 sec delay // do some background task withContext(Dispatchers.Main) { // call to UI thread } }Use liveData.postValue(value) en lugar de liveData.value = value . Se llama asíncrono.
De la documentación :
postValue : publica una tarea en un hilo principal para establecer el valor dado.
En mi caso, tuve que agregar Dispatchers.Main a los argumentos de lanzamiento y funcionó bien:
val job = GlobalScope.launch(Dispatchers.Main) { delay(1500) search(query) }