Curso de Flutter con Firebase

Modelo e repositório Firestore para BLoC

Curso de Flutter con Firebase

Contenido del curso

Modelo e repositório Firestore para BLoC

Resumen

Conectar Flutter con Firestore requiere una arquitectura clara que separe el modelo de datos del repositorio que comunica con la base. Aquí aprenderás a crear un model IncomeExpense y un repository que hace fetch de transacciones desde Cloud Firestore, listo para integrarse con BLoC.

Cómo defines el modelo IncomeExpense en Flutter

El modelo es la representación en Dart de cada documento que vive en Firestore. Tu archivo income_expense_model.dart dentro de la carpeta models debe reflejar exactamente los campos que ya configuraste en la colección.

Qué propiedades necesita la clase

La clase IncomeExpense arranca con cinco propiedades alineadas con los campos del documento en Firestore [01:00]:

  • id de tipo String, opcional, porque Firestore lo autogenera al crear el documento.
  • amount de tipo int, marcado como final para almacenar el monto.
  • description de tipo String para el detalle de la transacción.
  • date de tipo DateTime, que mapea desde un Timestamp de Firestore.
  • type de tipo String, que distingue entre income y expense.

En el constructor, id queda como this.id sin requerirlo, mientras que el resto se marca con required this.amount, required this.description, required this.date y required this.type.

¿Por qué el id no es requerido en el modelo? Porque Firestore lo genera automáticamente cuando creas un documento nuevo. Solo lo recibes al leer datos, no al escribirlos.

Cómo usar factory fromJson y toJson con Timestamp

Para que el modelo viaje entre Dart y Firestore necesitas dos puentes: un factory que recibe un Map<String, dynamic> y un método toJson que devuelve otro Map<String, dynamic> [03:30].

El truco está en el campo date. Firestore lo entrega como Timestamp, no como DateTime, así que el casteo se hace así: (json['date'] as Timestamp).toDate(). Para usar el tipo Timestamp debes importarlo desde la librería cloud_firestore.

El método toJson simplemente retorna un mapa con las claves amount, description, date y type igualadas a las variables de la instancia.

Cómo creas el repositorio que conecta con Firestore

El repositorio centraliza toda la comunicación con la base de datos. Crea el archivo income_expense_repository.dart y dentro define una clase con una propiedad FirebaseFirestore firestore [06:00].

La instalación del paquete se hace desde la terminal con:

bash flutter pub add cloud_firestore

Después corres el comando de configuración de FlutterFire para regenerar el archivo firebase_options.dart y dejar listo el proyecto.

La inicialización del repositorio queda así, instanciando Firestore por defecto:

dart class IncomeExpenseRepository { final FirebaseFirestore firestore;

IncomeExpenseRepository({FirebaseFirestore? firestore}) : firestore = firestore ?? FirebaseFirestore.instance; }

¿Para qué sirve un repository en Flutter? Es la capa que aísla la lógica de acceso a datos. Tu UI y tu BLoC nunca hablan directo con Firestore, sino con el repositorio, lo que facilita testear y cambiar el origen de datos.

Cómo implementar fetchTransactions de forma asíncrona

La función fetchTransactions es asíncrona y devuelve un Future<List<IncomeExpense>>. Dentro de un bloque try/catch haces la consulta a la colección transactions, que debe llamarse exactamente igual que en Firestore [09:00].

El flujo es directo:

  1. Ejecutas await firestore.collection('transactions').get() y guardas la respuesta en una variable query.
  2. Recorres query.docs.map((doc) => ...) para construir cada objeto IncomeExpense.
  3. Dentro del map asignas id: doc.id, amount: doc['amount'], date: (doc['date'] as Timestamp).toDate(), description: doc['description'] y type: doc['type'].
  4. Cierras con .toList() para devolver una lista concreta.

En el catch lanzas una excepción con un mensaje claro: throw Exception('Error fetching data on FetchTransactions: $e'). Así, cuando algo falle, sabrás exactamente en qué función ocurrió.

Por qué este patrón prepara el terreno para BLoC

Con el modelo tipado y el repositorio aislado, BLoC solo se preocupa de orquestar estados y eventos. El modelo garantiza que los datos lleguen con tipos correctos en Dart, y el repositorio asegura que la conversión entre Timestamp y DateTime ocurra una sola vez, en un solo lugar.

Esta separación es la que permite que mañana, si decides migrar de Firestore a otra base, solo tengas que reescribir el repositorio sin tocar tu UI ni tu lógica de negocio.

¿Ya tienes lista tu colección transactions en Firestore? Cuéntame en los comentarios qué campos agregaste a tu modelo.