Flutter, Bloc and Firestore Stream, the perfect match, if used the right way!

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

Esta story tambien esta disponible en español: Flutter, Bloc y Firestore Stream, ¡la combinación perfecta, si se usa de la manera correcta!

My very first Flutter Experience

So they gave me 10 days of training, by myself, starting with the flutter cookbook (https://flutter.dev/docs/cookbook).

After 2 days of training, honestly i was already in love with Flutter, one code for Android and iOS, and desktop and web in the future (which is now, Flutter 2.0.0…YEAY!!!!), that’s just amazing.

So at the end of my training, ready to start, they asked me if AWS would be a good choice for the backend, and yeah, it certainly is, but i told them, “have you been also considering Firebase?” I mean, it’s a google platform, so compatibility with Flutter are probably like 100%. And so guess what, after that, i finally start developing my first app with Flutter and Firebase. Happy me!

The way from dev to prod with my first app, was not an easy path, i have been struggling a lot with some big mistakes that almost cost the company some money with google billing.

So i am going to try to list some of them so i hope that it won’t happen to you.

Let’s begin

The StreamBuilder thing

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...
}
});

In those few line of code, there are many things wrong. The first one is calling each time FirebaseFirestore.instance, FirebaseFirestore should be instantiated as a Singleton and for that you can use the injectable plugin (https://pub.dev/packages/injectable) and use something like

getIt<FirebaseFirestore>()

to call it.

Next, the DocumentSnapshot HAS to be declared in your initState if you are using a StatefullWidget. If you are not doing so, each time your widget is being rebuilt, your stream will be initiated again until disposing the widget. Imagine it’s a QuerySnapshot with hundreds of documents, you will reach the free 50,000 documents reading in no time and it can be even worth if you are using setState inside the builder as it will cause an infinite loop of rebuild and you don’t want that.

Now if you really want to do it correctly, write your repository using injectable again, use the decorator @lazySingleton, take profit of dependency injection to inject the FirebaseFirestore.instance to your class, it will be much more cleaner. Here is an example of how i am normally doing it (kind of):

Normally, error should be handled inside the repository as well, i am using Either from the package Dartz (https://pub.dev/packages/dartz) for that, but it’s for another story.

Using Stream with Bloc

And with that, comes listening to Stream using the bloc pattern.

Actually this all come from a question i found on slack the other day and i have been helping him to implement this. I think it’s a legit question and the answer doesn’t come straight forward to your mind.

To be able to do this, you will need to have two events, one to call your repository stream method and listen to it and another one to call when new data are being received.

To keep the same example as earlier, let’s say we want to listen to this getLogs() method, i will need to create those two events:

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

now, in our bloc file, we can implement the logic to yield the state we want, but first we need to subscribe on the events of the stream and Stream.listen return a StreamSubscription so we have:

StreamSubscription<List<Log>> _logsStreamSubscription;

Now we can keep going and yield our state based on the events:

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
}
);

And that’s basically it, now you can call BlocBuilder in your StatelessWidget and do everything you want with your data, knowing that it will be the only part being rebuild when new data are coming;

And don’t forget to cancel your subscription as well, just override the Bloc close() method, and cancel the subscription in there.

Here is a full example of how i am using it:

You can see that i am using freezed for the code generation and Either for the error handling, if you want to know more about that, there are a lot of tutorials around here who will explain how it works better than me.

Well that was quite a story, and it is just my point of view, we all know that there are many and i am not pretending that this is the best one. This is just one possibility and there are many more, a that’s one the greatest thing we have, we can always learn new stuff, never bored.

Feel free to let me know what you think, if you like or dislike and why.

Happy coding to everyone!

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