Guía de Asincronía en Android: De AsyncTask (Legacy) a Corrutinas en Jetpack Compose
- 👤 Andrés Cruz
Cuando se desea realizar un llamado asíncrono en entornos web, solemos recurrir a AJAX o Fetch. En Android, aunque el concepto es similar, la ejecución es más compleja debido a la gestión de hilos y el ciclo de vida del dispositivo.
Enfoque Moderno: Corrutinas de Kotlin y Jetpack Compose
Hoy en día, la forma recomendada de manejar la asincronía es mediante Kotlin Coroutines. A diferencia de los hilos tradicionales, las corrutinas son ligeras y, lo más importante, son conscientes del ciclo de vida.
Uso de ViewModel y viewModelScope
En lugar de declarar la tarea dentro de la vista, la lógica se mueve a un ViewModel. Esto permite que la petición sobreviva a la rotación de pantalla.
// Enfoque moderno en el ViewModel
class MyViewModel : ViewModel() {
var uiState by mutableStateOf<String?>(null)
private set
fun fetchData() {
viewModelScope.launch {
// Esto se ejecuta en segundo plano sin bloquear la UI
val result = repository.makeNetworkCall()
uiState = result // Se actualiza el estado automáticamente
}
}
}Implementación en Jetpack Compose
En Jetpack Compose, la interfaz reacciona automáticamente a los cambios de estado. Ya no necesitamos interfaces de respuesta (delegates); la UI simplemente se "re-compone" cuando llega la data.
@Composable
fun MyScreen(viewModel: MyViewModel = viewModel()) {
val state = viewModel.uiState
Column {
if (state == null) {
CircularProgressIndicator() // Cargando...
LaunchedEffect(Unit) { viewModel.fetchData() }
} else {
Text(text = "Resultado: $state")
}
}
}Simulando la opción "Deshacer" (Undo)
El caso de uso de "Deshacer" de Gmail es un ejemplo perfecto para las corrutinas. En lugar de complejos hilos que duermen, usamos la función delay() de Kotlin, que no bloquea el hilo principal.
fun deleteItemWithUndo(item: Item) {
viewModelScope.launch {
showUndoSnackbar = true
try {
// Esperamos 5 segundos de forma asíncrona
withTimeout(5000) {
// Si el usuario no cancela en este tiempo...
repository.deletePermanently(item)
}
} catch (e: CancellationException) {
// Si el usuario presiona "Deshacer", cancelamos la corrutina
showUndoSnackbar = false
}
}
}Enfoque Legacy: AsyncTask y Threads manuales
Históricamente, en Android se utilizaban los Thread manuales o la clase AsyncTask (ahora obsoleta) para ejecutar tareas en segundo plano dentro de una Actividad o Fragment:
// Forma Legacy con Threads
new Thread(new Runnable() {
public void run() {
// Tareas costosas aquí
}
}).start();Problemas del enfoque Legacy
El mayor problema es el Ciclo de Vida. Si el usuario rota la pantalla (provocando un recreate de la actividad) o navega hacia atrás mientras la petición está en curso, se producen fugas de memoria (memory leaks) o la petición simplemente "se pierde" al intentar actualizar una interfaz que ya no existe.
Para mitigar esto en el modelo Legacy, se solían implementar interfaces como AsyncResponse para delegar el resultado, pero la complejidad del código aumentaba exponencialmente al intentar manejar los cambios de configuración.
public class MyFragment extends Fragment implements
AsyncResponse {
...
myAsyncTask = new MyAsyncTask();
…
public void processFinish(String output) {
//El método processFinish() es invocado cuando la clase
//asíncrona llamada MyAsyncTask ejecuta el método onPostExecute(result) method.
}
}Conclusión
Mientras que el enfoque Legacy nos obligaba a pelear contra el ciclo de vida de la Actividad, el enfoque de Compose y Corrutinas nos permite escribir código asíncrono que parece secuencial, siendo mucho más robusto y fácil de mantener.
Acepto recibir anuncios de interes sobre este Blog.
Domina la programación asíncrona en Android: de los hilos y AsyncTask (Legacy) a las potentes Corrutinas de Kotlin y Jetpack Compose.