Attempting to learn Dart and Flutter: Part 3
March 29, 2019 • 3 minutes to read
Intro
So Firestore a NoSQL database, is kind of like the second generation of Firebase. Nice product, I liked it, even if you need to rethink about a lot of stuff. Lately, I've been playing with a personal project "I will have more info about this one in the next month :)" with Flutter and Firestore. Here some stuff I found with this experiment.
First Firestore/Firebase give you quite a lot, analytics, authentication, email verification, real-time data, etc. So for an MVP of the product, I want to build it's the way to go I think. Autoscale, dashboard, etc, all came with that. Pretty nice for a one-man army. Also, the integration with Flutter is really great. For sure it's a help when both products are own by the same company.
BLoC (Business Logic Component)
So after reading a lot online, and see a lot of conference on Youtube I see Google encouraged the developer to use the BLoC Pattern. Yes at the beginning that was quite hard to understand the flow and everything. But remind me a lot of what I was doing when I was using redux observable with react-native. So finally the BLoC pattern is kind of like React Context + RxJS. First, you create a BLoC who is a simple class. And after that, you create a provider who is an InheritedWidget
. This lets you have access to the BLoC himself from anywhere in the app if you wrap the full app with the provider.
youtube:RS36gBEp8OI
Like React Context this let you jump some Widget. So no need to do props drill. But what is a BLoC? For me, because of my Redux or Mobx experience, I think of it like a Reducer or a Store. The UI trigger some action in this BLoC and this one past down the new state. I know maybe not the best way of thinking about this, but hey I'm not a master on Flutter, I learn on my free time :)
How to use BLoC with Firestore
So Firestore gives you already a stream when you retrieved data from the DB. Really nice you can pass this directly to the StreamBuilder if you want.
1return StreamBuilder(2 stream: Firestore.instance.collection('my collections').snapshots()3);
But what should I do if I want to move out of Firestore later? My UI depends too much of this if I wrap all the listener like that. So for me, I like to remove the business logic from the UI to keep it clean, and easier to refactor later.
The thing is it's easy to say perfect go and use BLoC + Firestore, but you don't gonna find too much documentation online. And the tutorials I found is not quite like I really want.
What do I want?
- Using BLoC
- But getting the typing working for me so I want model class.
What do I mean by But getting the typing working for me so I want model class.? If you use Firestore you receive from the stream a QuerySnapshot or DocumentSnaphot. This thing will return you a Map who can be used in Dart. The thing is a Map is too much dynamic for me. I'm not coming to a typing language to start doing dynamic stuff.
In Dart you can have multiple Constructor, it's a really good place if you want to create a model class who can parse himself from json or map.
1class TodoModel {2 final String title;3 final bool completed;45 TodoModel({6 this.title,7 this.completed = false,8 });910 TodoModel.fromMap(Map<dynamic, dynamic> map)11 : title = map['title'],12 completed = map['completed'];13}
As you can see here my constructor fromMap will take the map and parsed it to my object. You see the map['title']
stuff? I want that to just stay here, I don't want to start using this everywhere in my app. So that's why I say I want it type to an object. So how can we make Firestore working this way? I mean I want to be able to do inside my UI something like that.
1return Text(todo.title);
Here what I did.
My solution
P.S this is just based on some experimentation, and for my use case, I'm still learning flutter so please, I know this is surely not optimum :)
P.S I use rxdart
The Repository is just a wrapper around my FirestoreProvider
1class TodosBloc {2 final _repository = Repository();3 final _todos = BehaviorSubject<QuerySnapshot>();45 Observable<List<TodoModel>> get todos => _todos.stream.transform(_todosTransformer());67 void dispose() async {8 await _todos.drain();9 _todos.close();10 }1112 void getTodos() {13 _repository.getTodos().listen(_todos.sink.add);14 }1516 StreamTransformer<QuerySnapshot, List<TodoModel>> _todosTransformer() {17 return StreamTransformer.fromHandlers(handleData: (18 QuerySnapshot data,19 EventSink<List<TodoModel>> sink,20 ) {2122 final todos = data.documents.map((snap) => TodoModel.fromMap(snap.data)).toList();2324 sink.add(todos);25 }, handleError: (error, stackTrace, sink) {26 sink.addError(error);27 });28 }29}
Here is nothing crazy, I will make another post about a tutorial about it maybe later, here it's just to show you how I did the main point here is I listen to my stream from Firestore who is the stream who return a list of map to a new stream who transform his values to a list of TodoModel. Now when I want to use it I can do that.
1return StreamBuilder(2 stream: todosBloc.todos,3 builder: (BuildContext context, AsyncSnapshot<List<TodoModel>> snapshot) {4 switch (snapshot.connectionState) {5 case ConnectionState.waiting:6 return Center(7 child: CircularProgressIndicator(),8 );9 default:10 return ListView.builder(11 itemBuilder: (BuildContext context, int index) {12 final todo = snapshot.data[index];13 return Container(14 child: Text(todo.title),15 );16 },17 itemCount: snapshot.data.length,18 );19 }20 },21);
So now I can use my data like I want, as my class model. The autocomplete is there, and the linting also. So I cannot misspell something everywhere, the only place where I need to make sure I don't make type is the class model himself.
End word
I hope you enjoy this little post, and maybe even learn something. Let me know in the comments if you have any question :)
Happy Coding :)