Manejo de Estados y Eventos en Flutter con Bloc para Finanzas

Clase 19 de 30Curso de Flutter con Firebase

Resumen

La arquitectura BLoC (Business Logic Component) es una de las formas más eficientes de gestionar el estado en aplicaciones Flutter. A través de un enfoque estructurado, permite separar la lógica de negocio de la interfaz de usuario, facilitando el mantenimiento y la escalabilidad de nuestras aplicaciones. En este artículo, exploraremos cómo implementar esta arquitectura para gestionar ingresos y gastos en una aplicación, desde la creación de estados hasta la conexión con repositorios de datos.

¿Cómo estructurar la arquitectura BLoC para gestionar transacciones?

Para implementar correctamente la arquitectura BLoC en nuestra aplicación de ingresos y gastos, necesitamos crear una estructura organizada de archivos. Comenzaremos creando una carpeta llamada "income_expense" dentro de la carpeta "blocs" para albergar nuestros componentes principales:

  1. State: Define los diferentes estados de nuestra aplicación.
  2. Event: Define las acciones que pueden ocurrir en nuestra aplicación.
  3. Bloc: Actúa como centro de control, conectando eventos, estados y repositorios.

¿Cómo definir los estados de nuestra aplicación?

El primer paso es crear un archivo state.dart que contendrá una clase abstracta y sus implementaciones específicas:

abstract class IncomeExpenseState {}

class TransactionLoading extends IncomeExpenseState {}

class TransactionLoaded extends IncomeExpenseState {
  final List<TransactionModel> transactions;
  
  TransactionLoaded(this.transactions);
}

class TransactionError extends IncomeExpenseState {
  final String message;
  
  TransactionError(this.message);
}

En este código, hemos definido:

  • Una clase abstracta IncomeExpenseState que servirá como base para todos nuestros estados.
  • Tres estados específicos:
    • TransactionLoading: Cuando los datos están cargando.
    • TransactionLoaded: Cuando los datos se han cargado exitosamente.
    • TransactionError: Cuando ocurre un error durante la carga.

Es importante crear un estado para cada posible situación de nuestra aplicación, lo que nos permitirá manejar adecuadamente la interfaz de usuario según el estado actual.

¿Cómo definir los eventos de nuestra aplicación?

El siguiente paso es crear un archivo event.dart para definir las acciones que pueden ocurrir:

abstract class IncomeExpenseEvent {}

class LoadTransactions extends IncomeExpenseEvent {}

Por ahora, solo hemos definido un evento LoadTransactions que se utilizará para cargar las transacciones desde nuestra base de datos. A medida que nuestra aplicación crezca, podríamos añadir más eventos como AddTransaction o DeleteTransaction.

¿Cómo implementar el BLoC para conectar todo?

Finalmente, creamos un archivo bloc.dart que actuará como el centro de control:

class IncomeExpenseBloc extends Bloc<IncomeExpenseEvent, IncomeExpenseState> {
  final IncomeExpenseRepository repository;
  
  IncomeExpenseBloc(this.repository) : super(TransactionLoading()) {
    on<LoadTransactions>((event, emit) async {
      try {
        emit(TransactionLoading());
        final transactions = await repository.fetchTransactions();
        emit(TransactionLoaded(transactions));
      } catch (e) {
        emit(TransactionError("Error loading transactions: $e"));
      }
    });
  }
}

En este BLoC:

  1. Extendemos la clase Bloc proporcionada por la librería bloc, especificando los tipos de evento y estado.
  2. Inyectamos el repositorio que contiene la lógica para acceder a los datos.
  3. Inicializamos el estado inicial como TransactionLoading.
  4. Registramos un manejador para el evento LoadTransactions que:
    • Emite un estado de carga
    • Intenta obtener las transacciones del repositorio
    • Emite un estado de éxito con los datos o un estado de error si algo falla

¿Por qué usar try-catch en nuestro BLoC?

El uso de bloques try-catch es fundamental para manejar posibles errores durante la obtención de datos. Esto nos permite:

  • Capturar excepciones que puedan ocurrir durante las operaciones asíncronas
  • Emitir estados de error con mensajes informativos
  • Evitar que la aplicación se bloquee ante fallos inesperados

¿Cómo funciona el flujo de datos en esta arquitectura?

Con esta implementación, el flujo de datos sería:

  1. La UI dispara un evento LoadTransactions
  2. El BLoC recibe el evento y emite un estado TransactionLoading
  3. El BLoC llama al repositorio para obtener los datos
  4. Dependiendo del resultado, el BLoC emite un estado TransactionLoaded o TransactionError
  5. La UI se actualiza según el nuevo estado

Este enfoque desacopla completamente la interfaz de usuario de la lógica de negocio, permitiendo un código más limpio, testeable y mantenible.

La arquitectura BLoC nos proporciona una forma elegante de gestionar el estado en nuestras aplicaciones Flutter. Al separar claramente las responsabilidades entre estados, eventos y la lógica de negocio, creamos aplicaciones más robustas y fáciles de mantener. ¿Has implementado BLoC en tus proyectos? ¿Qué otras arquitecturas has probado para gestionar el estado? Comparte tu experiencia en los comentarios.