Flutter, Bloc y Firestore Stream, ¡la combinación perfecta, si se usa de la manera correcta!

Version disponible en Français: Flutter, Bloc and Firestore Stream, la combinaison parfaite, si utilisée de la bonne manière!

This story is also available in english: Flutter, Bloc and Firestore Stream, the perfect match, if used the right way!

Mi primera experiencia con Flutter

Entonces me dieron 10 días de entrenamiento, solo, empezando con el cookebook de flutter (https://flutter.dev/docs/cookbook).

Después de 2 días practicando, honestamente ya estaba enamorado de Flutter, un código para Android e iOS, y escritorio y web en el futuro (hoy, ya lo tenemos, Flutter 2.0.0… OLEE !!!!), eso es simplemente asombroso.

Entonces, al final de mi capacitación, listo para empezar, me preguntaron si AWS sería una buena opción para el backend, y sí, claro que lo es, pero les dije, "¿también han estado considerando Firebase?" Quiero decir, es una plataforma de Google, por lo que la compatibilidad con Flutter es probablemente del 100%. Y adivinen qué, después de eso, finalmente empiece a desarrollar mi primera aplicación con Flutter y Firebase. ¡Guai!

El camino de dev a prod con mi primera aplicación, no fue un camino fácil, he estado luchando mucho con algunos grandes errores que casi le cuestan a la compañía algo de dinero con la facturación de Google.

Así que voy a intentar enumerar algunos de ellos, así que espero que no te suceda.

Empezamos

El StreamBuilder

StreamBuilder<DocumentSnapshot>(
stream: FirebaseFirestore.instance.collection('myCollection').doc(item.id).snapshots(),
builder: (context, AsyncSnapshot<DocumentSnapshot> snapshot) {
if(snapshot.hasData) {
//Parsing your data, using setState(), etc...
} else {
//CircularProgressIndicator, handling error, etc...
}
});

En esas pocas líneas de código, hay varias cosas mal. El primero es llamar cada vez que FirebaseFirestore.instance, FirebaseFirestore debe instanciarse como Singleton y para eso puede usar el plugin injectable (https://pub.dev/packages/injectable) y usar algo como

getIt<FirebaseFirestore>()

para llamarlo.

A continuación, el DocumentSnapshot TIENE que ser declarado en su initState si está utilizando un StatefullWidget. Si no lo haces, cada vez que se reconstruya su widget, el Stream se iniciará nuevamente hasta que se elimine el widget. Imagina que es un QuerySnapshot con cientos de documentos, alcanzará la lectura gratuita de 50,000 documentos en poco tiempo e incluso puede ser peor si está usando setState dentro del constructor, ya que causará un bucle infinito de reconstrucción y no lo deseas.

Ahora, si realmente quieres hacerlo correctamente, escribe tu repositorio usando injectable nuevamente, usa el decorador @lazySingleton, aprovecha la inyección de dependencia para inyectar la instancia FirebaseFirestore.instance a tu clase, será mucho más limpio. Aquí hay un ejemplo de cómo lo hago normalmente (más o menos):

Normalmente, el error también debe manejarse dentro del repositorio, estoy usando Either del paquete Dartz (https://pub.dev/packages/dartz) para eso, pero es para otra historia.

Usando Stream con Bloc

Y con eso, viene escuchar Stream usando el patrón de bloque.

En realidad, todo esto proviene de una pregunta que encontré en Slack el otro día y lo he estado ayudando a implementar esto. Creo que es una pregunta legítima y la respuesta no te viene directamente a la mente.

Para poder hacer esto, necesitaras tener dos eventos, uno para llamar a tu Stream del repositorio y escucharlo y otro para llamar cuando se reciben nuevos datos.

Para mantener el mismo ejemplo que antes, digamos que queremos escuchar este método getLogs(), tendras que crear esos dos eventos:

const factory LogEvent.watchLogsAsked() = WatchLogsAsked;
const factory LogEvent.logsReceived(List<Log> data) = LogsReceived;

ahora, en nuestro archivo de Bloc, podemos implementar la lógica para obtener el State que queremos, pero primero debemos suscribirnos a los eventos del Stream y Stream.listen devuelve un StreamSubscription. Entonces tenemos:

StreamSubscription<List<Log>> _logsStreamSubscription;

Ahora podemos seguir y “yield” nuestro State en función de los eventos:

yield* event.map(
watchLogsAsked: (WatchLogsAsked _) async* {
yield state.copyWith(logsAreLoading: true);
await _logStreamSubscription?.cancel();
_logsStreamSubscription = _repository.getLogs().listen((data) => add(LogEvent.logsReceived(data)));
},
logsReceived: (LogsReceived data) async* {
//Do your stuff here and yield your state
}
);

Y eso es básicamente todo, ahora puedes llamar a BlocBuilder en tu StatelessWidget y hacer todo lo que quiera con sus datos, sabiendo que será la única parte que se reconstruirá cuando lleguen nuevos datos;

Y no te olvides cancelar tu suscripción también, simplemente “override” el método Bloc close() y cancela la suscripción allí.

Aquí esta un ejemplo completo de como lo uso:

Puedes ver que estoy usando freezed para la generación de código y para el manejo de errores, si quieres saber más sobre eso, hay muchos tutoriales por aquí que explicarán cómo funciona mejor que yo.

Bueno, esa fue toda una historia, y es solo mi punto de vista, todos sabemos que hay muchos y no pretendo que este sea el mejor. Esta es solo una posibilidad y hay muchas más, y esa es una de las mejores cosas que tenemos, siempre podemos aprender cosas nuevas, nunca nos aburrimos.

No dudes en hacerme saber lo que piensas, si te gusta o no y por qué.

Happy coding a todos!

Flutter Developer enthousiast since 2018

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store