Utilidad e implementación de funciones asincrónicas en Flutter


Avatar de Pedro Cortez

A veces, ciertas operaciones tardan demasiado en resolverse para que el widget pueda utilizarlas cuando se está construyendo. Por lo tanto, hay que decirle al widget que espere a la resolución antes de terminar la construcción. Aquí es donde entran en juego las funciones asíncronas.


funciones async en Flutter

¿Qué es un dato asíncrono?

Un dato asíncrono es aquel que no está disponible inmediatamente cuando se construye tu widget. A diferencia de un dato síncrono, que se obtiene directamente (como una simple suma), un dato asíncrono requiere un tiempo para ser recuperado o procesado. Esto ocurre frecuentemente con tareas como consultar una API remota, leer un archivo en el disco o realizar un cálculo complejo.

Pero entonces podríamos preguntarnos: ¿Por qué no simplemente esperar a que estas tareas terminen antes de continuar? La respuesta está en la forma en que funcionan Flutter y Dart.

Entender qué es el Event Loop

Para comprender mejor por qué las funciones asíncronas son necesarias, primero debemos entender cómo se construye una aplicación en Flutter.

En Flutter, toda la construcción de la aplicación se basa en un único hilo de ejecución principal, llamado thread UI. Este hilo debe manejar tanto el renderizado de la interfaz como la ejecución del código, procesando las tareas una por una. A este mecanismo de construcción de la aplicación se le llama Event Loop.

Imagina a un director de orquesta que dirige una sinfonía y que solo puede dar una instrucción a la vez, pero debe gestionar varios músicos que tocan en momentos diferentes.

Así es como funciona:

  • Pila de llamadas (Call Stack): Aquí se ejecutan las tareas inmediatas (síncronas), como mostrar un texto o realizar un cálculo rápido. Se procesan una por una, en orden.
  • Cola de eventos (Event Queue): Cuando una tarea toma tiempo (como cargar datos desde una API), se coloca en esta cola de espera. El Event Loop la recupera y la ejecuta solo cuando la pila de llamadas está vacía.

Veamos un ejemplo:

void main() {
  print("Inicio");
  Future.delayed(Duration(seconds: 2), () => print("Tarea lenta"));
  print("Fin");
}

Aquí, Flutter primero manejará las tareas síncronas, que pueden ejecutarse de inmediato, y luego volverá más tarde para la tarea asíncrona. Esto es lo que se mostrará en la consola:

Inicio
Fin
(2 segundos después)
Tarea lenta

En este caso, el Event Loop ejecuta primero las tareas síncronas (print("Inicio") y print("Fin")), y luego, cuando la pila está vacía, busca la tarea asíncrona en la cola de eventos después del retraso de 2 segundos. Sin este mecanismo, la aplicación quedaría bloqueada durante esos 2 segundos, arruinando la experiencia del usuario.

¿Por qué usar funciones asíncronas?

Las funciones asíncronas (con Future, async y await) son la forma en que se «dialoga» con el Event Loop. Dado que Dart no puede ejecutar múltiples tareas en paralelo en su hilo principal, debemos delegar las operaciones largas a este sistema para evitar bloquear la interfaz. Aquí es donde resultan indispensables:

Las funciones asíncronas permiten:

  • Mantener la fluidez de la aplicación: Si cargas datos desde una API sin funciones asíncronas, el hilo UI esperaría la respuesta, congelando la pantalla. Con async, el Event Loop toma el control y la aplicación sigue siendo interactiva.
  • Gestionar tareas dependientes del tiempo: Las llamadas de red (HTTP), el acceso a archivos o bases de datos e incluso la descarga de imágenes son impredecibles en cuanto a duración. Las funciones asíncronas permiten ejecutarlas sin detener toda la aplicación.
  • Optimizar operaciones costosas: Un cálculo pesado o el procesamiento de imágenes puede tardar varios segundos. Haciéndolo asíncrono, permitimos que el Event Loop lo maneje sin afectar el renderizado.

Aquí tienes un ejemplo de una función asíncrona que le dice al Event Loop: «Ocúpate de otra cosa durante estos 3 segundos y vuelve cuando esté listo»:

Future<String> fetchData() async {
  await Future.delayed(Duration(seconds: 3)); // Simula una petición API
  return "¡Datos recuperados!";
}

void main() async {
  print("Cargando...");
  String data = await fetchData();
  print(data);
}

Desventajas de una función asíncrona

A primera vista, podría parecer que sería una buena idea hacer que todas nuestras funciones sean asíncronas para cargar la página más rápido. Sin embargo, la realidad es más compleja. No existe una solución mágica y las funciones asíncronas tienen ciertos inconvenientes, por lo que su uso debe limitarse a lo estrictamente necesario:

  • Mayor complejidad del código: Usar funciones asíncronas puede hacer que el código sea más difícil de leer y depurar, ya que el flujo de eventos se vuelve menos predecible.
  • Gestión de múltiples funciones en secuencia: Aunque las funciones asíncronas evitan bloquear el hilo principal, no todas se ejecutan en paralelo por defecto. Debes gestionar el orden de ejecución para evitar bloqueos potenciales o comportamientos inesperados. A veces, puede ser más sencillo ejecutar ciertas operaciones de manera síncrona si solo tardan una fracción de segundo.
  • Imprevisibilidad de los resultados: Con funciones asíncronas, puede ser difícil predecir exactamente cuándo se completará una tarea. Por ejemplo, si dependes de una llamada de red, debes anticipar posibles retrasos, fallos de conexión o errores.

Para operaciones rápidas que no requieren obtener recursos remotos, el uso de funciones asíncronas no es necesario e incluso puede ser contraproducente. Corres el riesgo de desorganizar el Event Loop sin motivo.

¿Cómo implementar funciones asíncronas en Flutter con async y await?

En Flutter (y Dart en general), la gestión de funciones asíncronas se realiza principalmente mediante las palabras clave async y await.

  • async: Esta palabra clave se usa para marcar una función como asíncrona. Esto significa que la función puede contener operaciones que tardarán un cierto tiempo en ejecutarse.
  • await: Esta palabra clave se usa dentro de una función marcada con async. Indica a Dart que espere a que se complete la operación asíncrona antes de continuar con la ejecución de la función.

Aquí tienes un ejemplo simple de una aplicación que muestra un texto en la pantalla después de un tiempo de carga (simulado) de 2 segundos:

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Async Await Demo',
      home: HomeScreen(),
    );
  }
}

class HomeScreen extends StatefulWidget {
  @override
  _HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  String _data = "Cargando...";

  // Función asíncrona que simula una tarea larga
  Future<void> _fetchData() async {
    await Future.delayed(Duration(seconds: 2)); // Simula una espera de 2 segundos
    setState(() {
      _data = "Datos recuperados después de 2 segundos";
    });
  }

  @override
  void initState() {
    super.initState();
    _fetchData(); // Llamada a la función asíncrona al iniciar la aplicación
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Async Await Demo'),
      ),
      body: Center(
        child: Text(
          _data,
          style: TextStyle(fontSize: 20),
        ),
      ),
    );
  }
}

Explicación del código

Aquí, la función asíncrona _fetchData simula una espera de 2 segundos con Future.delayed. Una vez que la espera ha terminado, actualiza la interfaz de usuario llamando a setState.

Al iniciarse, la aplicación muestra el texto «Cargando…». Luego, después de 2 segundos, el texto se actualiza a «Datos recuperados después de 2 segundos» gracias a la función asíncrona que se ejecuta en segundo plano.

Conclusión

Ahora que comprendes el papel de las funciones asíncronas, estás listo para recuperar recursos en línea y mostrarlos en tu aplicación de manera fluida y reactiva. Este mecanismo es esencial para hacer que una aplicación sea dinámica, y es la base de muchas aplicaciones modernas, como servicios de mensajería, plataformas de compras en línea o aplicaciones de gestión de listas.

Pero, ¿qué widget te permite recuperar y mostrar esta información en tiempo real? Eso es lo que exploraremos en los dos próximos artículos.

Guía introductoria al widget FutureBuilder en Flutter

Avatar de Pedro Cortez