Iniciando con Corrutinas de Kotlin en Android
Una breve introducción sobre las corrutinas que te servirá para aumentar la productividad y escribir código mas limpio y conciso en tus aplicaciones.
- ¿Que son las Corrutinas?
- ¿Cuál es la función de una Corrutina?
- ¿Que es el launch?
- Tipos de CoroutineScope
- ¿De que se encarga el Dispatchers?
- Manejo de excepciones usando Corrutinas
¿Cual es la diferencia entre Asíncrono y Síncrono?
El término “asíncrono” se refiere a algo que sucede de manera independiente a otro proceso y no necesariamente en el mismo orden. Por ejemplo, si tienes una aplicación que realiza una tarea asíncrona, esa tarea puede realizarse mientras la aplicación sigue funcionando y haciendo otras cosas. Esto es útil cuando quieres evitar bloquear el hilo de la interfaz de usuario (UI) mientras realizas una tarea que puede llevar mucho tiempo, como descargar datos de Internet.
Por otro lado, el término “síncrono” se refiere a algo que sucede en un orden específico y una tarea síncrona debe completarse antes de que otra tarea pueda comenzar. Por ejemplo, si tienes una aplicación que realiza una tarea síncrona, la aplicación no puede hacer otra cosa hasta que se complete esa tarea. Esto puede ser útil cuando quieres asegurarte de que una tarea se complete antes de que otra tarea comience, pero puede ser inconveniente si la tarea toma mucho tiempo y bloquea la interfaz de usuario (UI).
Un ejemplo aplicado en Android podría ser el de una tarea “asíncrona” para descargar datos de Internet mientras se muestra una pantalla de carga a los usuarios. En lugar de bloquear la interfaz de usuario (UI) mientras se realiza la descarga, se puede mostrar una pantalla de carga y realizar la descarga de manera asíncrona en segundo plano. De esta manera, los usuarios pueden seguir interactuando con la aplicación mientras se realiza la descarga y se puede actualizar la interfaz de usuario (UI) una vez que se completa la descarga.
Un ejemplo de una tarea síncrona en una aplicación Android podría ser leer un archivo de almacenamiento interno y procesar los datos contenidos en él. En este caso, es importante asegurarse de que se lea y procese el archivo completo antes de realizar cualquier otra tarea, por lo que se debe usar una tarea síncrona.
¿Que son las corutinas?
Las corrutinas son una forma de escribir código asíncrono de manera síncrona. Esto significa que puedes escribir código que se ejecuta de manera asíncrona de manera similar a como escribirías código síncrono, pero sin tener que lidiar con callbacks anidados y sin bloquear el hilo de interfaz de usuario (UI). Esto hace que sea más fácil escribir aplicaciones que realizan tareas asíncronas de manera más clara y legible.
Las corrutinas están disponibles a partir de Android 3.0 (Honeycomb) y se pueden usar con la biblioteca de soporte de AndroidX.
¿Cuál es la función de una Corrutina?
La función principal de las corrutinas en Android es facilitar la legibilidad al escribir código asíncrono de forma más clara. Las corrutinas permiten escribir código asíncrono de manera similar a como se escribe código síncrono, lo que hace que sea más fácil de leer y entender.
Además, las corrutinas permiten realizar tareas asíncronas de manera más eficiente y evitan el uso de callbacks anidados, lo que puede hacer que el código sea más fácil de mantener. También tienen menos impacto en el rendimiento que otras soluciones asíncronas, como los hilos, por lo que son una opción atractiva para realizar tareas asíncronas en Android.
En resumen, las corrutinas son una herramienta útil para escribir código asíncrono de manera más clara y legible, y para hacer que el código sea más fácil de mantener y tenga menor impacto en el rendimiento.
Para declarar una corrutina en Android, primero debes incluir la biblioteca de soporte de AndroidX en tu proyecto y agregar la dependencia en tu archivo build.gradle
.
dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'
}
Luego, en tu clase, debes importar la clase CoroutineScope
y crear una instancia de esta clase.
Para crear una corrutina, debes usar el método launch
o async
de CoroutineScope
. El método launch
se utiliza para crear una corrutina que no devuelve un resultado, mientras que el método async
se utiliza para crear una corrutina que devuelve un resultado. Ambas funciones toman un bloque de código lambda como argumento que contiene la lógica de la corrutina.
Ejemplo de una corutina que no devuelve un resultado:
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
class MyViewModel : ViewModel() {
fun doWork() {
viewModelScope.launch {
// Realiza el trabajo aquí
}
}
}
Ejemplo de cómo se podría declarar una corrutina que devuelve un resultado en Android:
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.async
class MyViewModel : ViewModel() {
fun getData(): Deferred<Data> {
return viewModelScope.async {
// Obtiene los datos aquí y devuelve el resultado
}
}
}
En ambos ejemplos, se crea una instancia de CoroutineScope
utilizando el extension property viewModelScope
y se usa el método launch
o async
para crear la corrutina. El bloque de código lambda que se pasa como argumento contiene la lógica de la corrutina y se ejecutará de manera asíncrona cuando se llame a la corrutina.
¿Que es el launch?
launch
es un método de la clase CoroutineScope
que se utiliza para crear una corrutina que no devuelve un resultado. Se utiliza para realizar tareas asíncronas que no necesitan devolver un valor al llamador, como enviar una solicitud de red o actualizar la interfaz de usuario (UI).
El método launch
toma un bloque de código lambda como argumento que contiene la lógica de la corrutina y crea una nueva corrutina que se ejecutará de manera asíncrona. El método launch
devuelve un objeto Job
que puedes usar para cancelar la corrutina si es necesario.
Ejemplo usando el método launch
para crear una corrutina que actualiza la interfaz de usuario (UI) en una aplicación Android:
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
class MyViewModel : ViewModel() {
fun updateUI() {
viewModelScope.launch {
// Obtiene los datos aquí
withContext(Dispatchers.Main) {
// Actualiza la interfaz de usuario (UI) con los datos aquí
}
}
}
}
En este ejemplo, se crea una instancia de CoroutineScope
utilizando el extension property viewModelScope
y se usa el método launch
para crear una corrutina que se ejecutará de manera asíncrona. La corrutina obtiene los datos necesarios y luego actualiza la interfaz de usuario (UI) con los datos utilizando el método withContext(Dispatchers.Main)
, que ejecuta el código en el hilo de la interfaz de usuario (UI).
Tipos de CoroutineScope
Existen varios tipos de CoroutineScope
que puedes usar para crear corrutinas en Android. Algunos de los más comunes son:
GlobalScope
: Es una instancia deCoroutineScope
que se ejecuta en un hilo global y no está vinculada a ningún componente de Android, como unaActivity
o unFragment
. Se puede usar para realizar tareas que no estén vinculadas a la interfaz de usuario (UI) o para iniciar una corrutina que sea independiente de la vida útil de cualquier componente de Android.viewModelScope
: Es un extension property deViewModel
que se puede usar para crear corrutinas que se cancelen cuando elViewModel
se destruye. Es útil para realizar tareas asíncronas que se relacionen con la vista, pero que no dependan de la vida útil de unaActivity
oFragment
en particular.lifecycleScope
: Es un extension property deLifecycleOwner
(comoActivity
oFragment
) que se puede usar para crear corrutinas que se cancelen cuando elLifecycleOwner
entra en un estado de destrucción. Es útil para realizar tareas asíncronas que dependan de la vida útil de un componente de Android en particular.
Ejemplo de cómo se podrían usar diferentes tipos de CoroutineScope
para crear corrutinas en una aplicación Android:
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
class MyViewModel : ViewModel() {
fun doWork() {
// Crea una corrutina en viewModelScope
viewModelScope.launch {
// Realiza el trabajo aquí
}
}
}
import androidx.lifecycle.ViewModel
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
class MyViewModel : ViewModel() {
fun doWork() {
// Crea una corrutina en GlobalScope
GlobalScope.launch {
// Realiza el trabajo aquí
}
}
}
class MyFragment : Fragment, LifecycleOwner {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Crea una corrutina en lifecycleScope
lifecycleScope.launch {
// Realiza el trabajo aquí
}
}
¿De que se encarga el Dispatchers?
El objeto Dispatchers
es una clase provista por Kotlin que proporciona acceso a diferentes hilos de ejecución y se utiliza a menudo con corrutinas para especificar en qué hilo se debe ejecutar una tarea.
Por ejemplo:
Dispatchers.Main
éste método se utiliza para ejecutar una tarea en el hilo de la interfaz de usuario (UI) y es útil para actualizar la interfaz de usuario (UI) desde una corrutina.Dispatchers.IO
se utiliza para ejecutar tareas de entrada/salida (I/O) como solicitudes de red y el métodoDispatchers.Default
se utiliza para ejecutar tareas en un hilo de trabajo por defecto.
Ejemplo de cómo se podría usar el objeto Dispatchers
para especificar en qué hilo se debe ejecutar una tarea en una corrutina en una aplicación Android:
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class MyViewModel : ViewModel() {
fun doWork() {
viewModelScope.launch {
// Realiza el trabajo en un hilo de trabajo por defecto
val result = withContext(Dispatchers.Default) {
// Realiza el trabajo aquí
}
// Actualiza la interfaz de usuario (UI) con el resultado en el hilo de la interfaz de usuario (UI)
withContext(Dispatchers.Main) {
// Actualiza la interfaz de usuario (UI) aquí
}
}
}
}
En este ejemplo, la corrutina se inicia en el hilo de trabajo por defecto y luego se usa el método withContext(Dispatchers.Default)
para ejecutar una tarea en el hilo de trabajo por defecto. Luego, se usa el método withContext(Dispatchers.Main)
para actualizar la interfaz de usuario (UI) con el resultado en el hilo de la interfaz de usuario (UI).
Manejo de excepciones usando Corrutinas
Las corrutinas te permiten manejar excepciones de manera similar a como lo harías en código síncrono. Puedes usar la instrucción
try
-catch
para atrapar excepciones y la instrucciónthrow
para lanzar excepciones.
Por ejemplo, aquí hay un ejemplo de cómo se podría manejar una excepción en una corrutina en una aplicación Android:
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
class MyViewModel : ViewModel() {
fun doWork() {
viewModelScope.launch {
try {
// Realiza el trabajo aquí
} catch (e: Exception) {
// Maneja la excepción aquí
}
}
}
}
En este ejemplo, se crea una corrutina y se usa la instrucción try
-catch
para atrapar cualquier excepción que pueda ocurrir durante la ejecución de la corrutina. Si se produce una excepción, se maneja en el bloque catch
.
También puedes lanzar excepciones desde una corrutina usando la instrucción throw
. Por ejemplo:
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
class MyViewModel : ViewModel() {
fun doWork() {
viewModelScope.launch {
// Realiza el trabajo aquí
if (errorOccurred) {
throw Exception("Error occurred")
}
}
}
}
En este ejemplo, se lanza una excepción si se produce un error durante la ejecución de la corrutina. La excepción puede ser atrapada y manejada en otro lugar del código, como se muestra en el primer ejemplo.
Es importante tener en cuenta que las excepciones lanzadas desde una corrutina no se propagarán automáticamente al hilo en el que se creó la corrutina. En su lugar, debes atrapar la excepción en la corrutina y manejarla allí o usar el método async
y el método await
para propagar la excepción al hilo que llamó a la corrutina.
Manejo de excepciones con async y await
Otra forma de manejar excepciones con corrutinas es usar el método async
y el método await
para propagar la excepción al hilo que llamó a la corrutina.
El método async
es similar al método launch
, pero devuelve un objeto Deferred
que puedes usar para obtener el resultado de la corrutina una vez que se complete. El método await
se usa para esperar a que se complete la corrutina y obtener su resultado. Si la corrutina lanza una excepción, el método await
propagará la excepción al hilo que llamó a la corrutina.
Aquí hay un ejemplo de cómo se podría manejar una excepción usando el método async
y el método await
en una aplicación Android:
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
class MyViewModel : ViewModel() {
fun doWork(): List<Result> {
val deferredResults = listOf(
viewModelScope.async {
// Realiza el trabajo y devuelve el resultado aquí
if (errorOccurred) {
throw Exception("Error occurred")
}
return@async result
}
)
return try {
deferredResults.awaitAll()
} catch (e: Exception) {
// Maneja la excepción aquí
emptyList()
}
}
}
En este ejemplo, se crea una lista de objetos Deferred
utilizando el método async
. Cada objeto Deferred
representa una corrutina que se ejecutará de manera asíncrona. Si alguna de las corrutinas lanza una excepción, el método awaitAll
propagará la excepción al hilo que llamó a la corrutina. La excepción se atrapa en el bloque catch
y se maneja allí.
Si usas el método awaitAll
para esperar a que se completen varias corrutinas y una de ellas lanza una excepción, el método awaitAll
propagará la excepción y todas las corrutinas restantes se cancelarán.
Por ejemplo, en el siguiente código:
val deferredResults = listOf(
viewModelScope.async {
// Realiza el trabajo y devuelve el resultado aquí
if (errorOccurred) {
throw Exception("Error occurred")
}
return@async result1
},
viewModelScope.async {
// Realiza el trabajo y devuelve el resultado aquí
return@async result2
}
)
try {
deferredResults.awaitAll()
} catch (e: Exception) {
// Maneja la excepción aquí
}
Si la primera corrutina lanza una excepción, el método awaitAll
propagará la excepción y cancelará la segunda corrutina. Si no se produce ninguna excepción, el método awaitAll
devuelve una lista con los resultados de las corrutinas.
También puedes usar el método async
y el método await
para ejecutar varias corrutinas de manera independiente y manejar cada excepción de manera individual:
val deferredResult1 = viewModelScope.async {
// Realiza el trabajo y devuelve el resultado aquí
if (errorOccurred) {
throw Exception("Error occurred")
}
return@async result1
}
val deferredResult2 = viewModelScope.async {
// Realiza el trabajo y devuelve el resultado aquí
return@async result2
}
try {
val result1 = deferredResult1.await()
val result2 = deferredResult2.await()
} catch (e: Exception) {
// Maneja la excepción aquí
}
En este ejemplo, cada corrutina se ejecuta de manera independiente y se maneja cada excepción de manera individual en el bloque catch
.
Una forma más elegante de manejar excepciones de corrutinas en Kotlin es usar el método runCatching
o el método try
de la clase Deferred
.
El método runCatching
es una función top-level que ejecuta un bloque de código y devuelve un objeto Result
que contiene el resultado de la operación o la excepción que se lanzó. Puedes usar el método isSuccess
para comprobar si la operación se realizó correctamente y el método exceptionOrNull
para obtener la excepción lanzada, si la hay.
Aquí hay un ejemplo de cómo se podría usar el método runCatching
para manejar excepciones en una corrutina en una aplicación Android:
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
class MyViewModel : ViewModel() {
fun doWork() {
viewModelScope.launch {
val result = runCatching {
// Realiza el trabajo aquí
if (errorOccurred) {
throw Exception("Error occurred")
}
return@runCatching result
}
if (result.isSuccess) {
// El trabajo se realizó correctamente
} else {
val exception = result.exceptionOrNull()
// Maneja la excepción aquí
}
}
}
}
En este ejemplo, se crea una corrutina y se usa el método runCatching
para ejecutar un bloque de código. Si la operación se realizó correctamente, el método runCatching
devuelve un objeto Result
con el resultado de la operación. Si se lanzó una excepción, el método runCatching
devuelve un objeto Result
con la excepción. Puedes usar el método isSuccess
para comprobar si la operación se realizó correctamente y el método exceptionOrNull
para obtener la excepción lanzada, si la hay.
Otra forma de manejar excepciones de corrutinas es usar el método try
de la clase Deferred
.
La forma de manejar excepciones de corrutinas usando el método try
de la clase Deferred
es similar a la forma de manejar excepciones usando el método runCatching
. El método try
es una función de extensión que devuelve un objeto Result
que contiene el resultado de la corrutina o la excepción que se lanzó.
Ejemplo de cómo se podría usar el método try
para manejar excepciones en una corrutina en una aplicación Android:
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
class MyViewModel : ViewModel() {
fun doWork(): List<Result> {
val deferredResults = listOf(
viewModelScope.async {
// Realiza el trabajo y devuelve el resultado aquí
if (errorOccurred) {
throw Exception("Error occurred")
}
return@async result
}
)
return deferredResults.map { it.try() }
}
}
En este ejemplo, se crea una lista de objetos Deferred
utilizando el método async
. Luego se usa el método try
para obtener una lista de objetos Result
que contienen el resultado de las corrutinas o las excepciones que se lanzaron. Puedes usar el método isSuccess
para comprobar si la corrutina se completó correctamente y el método exceptionOrNull
para obtener la excepción lanzada, si la hay.
Es importante tener en cuenta que el método try
solo se puede usar con objetos Deferred
y no con corrutinas lanzadas con el método launch
. Si quieres manejar excepciones de corrutinas lanzadas con el método launch
, debes usar el método runCatching
o la instrucción try
-catch
.
Espero éste post te haya servido para dar inicio al uso de la corrutinas y al como manejar excepciones de distintas formas! :D.