Flutter + Firestore + Provider tutorial

Generally I use my own backend services (I’ll cover them in my next articles) but recently I got quite curious on Firestore. Its scaling and speed abilities really intrigued me. However after spending nearly a day trying to look for tutorial and executing them I realised, most (all?) of them are out of date and won’t work. Flutter, Provider and Firestore undergo fast development therefore breaking changes are introduced time to time. Therefore I’ll show you current code, which works.

Disclaimer: Project Tested only on Android Simulator. Because I was too lazy to set-up the project on iOS 😅

TLDR

By the way, it won’t work out-of-the-box because your will need to create your own Firebase project which will be synced with your package id.

Setup

Flutter — Channel stable, v1.12.13+hotfix.5;

Firestore —0.13.0+1;

Provider — 4.0.0.

Let’s Start

Firestore

Congratulations! You have your own Firestore database!

In the nutshell, Firestore is NoSQL database which contains of Collections and Documents. Each Collection can have Documents and then that Documents can have Collection and so on. It’s pretty much Collection->Document->Collection->Documents,… If you want to get to know Firestore better, I’d recommend watching this series on YouTube. I loved it.

Let’s simple create a Collection of Users which will have a Document with many User ids. Each User will have their name.

Now we need to get back to Flutter and write a parser, to parse this data.
I’m defining my User model.

class User {
String id;
String name;

User(this.name);

factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);

Map<dynamic, dynamic> toJson() => _$UserToJson(this);

factory User.fromFirestore(DocumentSnapshot documentSnapshot) {
User user = User.fromJson(documentSnapshot.data);
user.id = documentSnapshot.documentID;
return user;
}
}

I have several methods. fromJson and toJson are standard methods for json serialisation in Flutter. You can read about this here.

What’s interesting for us is that our User in Firestore is a document. But if we parse it directly, we will loose its id. Therefore we need to make couple level parsing.
First we get json representation of User from document.

User user = User.fromJson(documentSnapshot.data);

And then manually assign id to the object.

user.id = documentSnapshot.documentID;

In this way we will have pimp’ed our User object to contain document ID, which can be very valuable.

After parsing is done, let’s move to Provider.

Provider

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
StreamProvider<List<User>>(create: (_) => streamOfUsers(), initialData: []),
],
child: MaterialApp(
title: 'Firestore DEMO',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
),
);
}

Stream<List<User>> streamOfUsers() {
var ref = Firestore.instance.collection('users');
return ref.snapshots().map((list) => list.documents.map((doc) => User.fromFirestore(doc)).toList());
}
}

We add StreamProvider with the type of <List<User>> because we expect such data. Then we provide stream, we should except.

Stream<List<User>> streamOfUsers() {
var ref = Firestore.instance.collection('users');
return ref.snapshots().map((list) => list.documents.map((doc) => User.fromFirestore(doc)).toList());
}

Here we simply say that we are expecting Collection of Users and map each object in the List.

Now all we are left is representation of the data in UI.

class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final users = Provider.of<List<User>>(context);

return Scaffold(
appBar: AppBar(
title: Text("Firestore DEMO"),
),
body: Column(
children: <Widget>[
Expanded(
child: ListView.builder(
itemCount: users.length,
itemBuilder: (context, index) {
User user = users[index];
return ListTile(
title: Text(user.name),
subtitle: Text(user.id),
trailing: IconButton(icon: Icon(Icons.delete), onPressed: () => _removeUser(user)),
leading: IconButton(icon: Icon(Icons.edit), onPressed: () => _updateUser(user)),
);
}),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
RaisedButton(
child: Text("Add"),
onPressed: () => Firestore.instance.collection('users').document().setData({'name': 'New name added'})),
],
),
],
));
}

void _removeUser(User user) {
Firestore.instance.collection('users').document(user.id).delete();
}

void _updateUser(User user) {
Firestore.instance.collection('users').document(user.id).setData({'name': "My name changed"});
}
}

Whole magic here

final users = Provider.of<List<User>>(context);

What Provider does for us here, it reacts to any change which was made for List<User> and gives us latest value of it. Afterwards whole Widget tree rebuilds.

You can try pressing on Edit, Remove or Add buttons to check the behaviour. Also try adding/removing/editing data via Firebase Console. Your app will correspond too!

And that’s it! You have fully working real-time database in Flutter.

By the way, please have a thought before jumping straight to Firestore. It’s definitely not for every app scenario. And regular SQL database might do much better. I’d strongly suggest to try first whole NoSQL experience and decide afterwards. Good luck!

I build kick-ass mobile apps @ https://isawthatguy.com || Product Virtuoso and Startup Freak

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