In this series of tutorials, I will walk you through the process of creating a TODO app with the Flutter framework. By the end, you will have a cross-platform TODO mobile application, which allows you to add, edit, remove and complete tasks, while persisting the data on Google Firebase. The series spans across 3 parts:
Part 1 – Setting up the development environment and creating the project
Part 2 – Implementing core functionality
Part 3 – Connecting your app to Google Firebase
Where we left off
After you have finished part 1 and 2, you should have a working app written in Flutter that lets you add tasks to your todo list and mark them as completed. In case you got lost, feel free to grab my code from Github and proceed with this tutorial.
What is Google Firebase?
Google Firebase is a BaaS (Backend-as-a-Serivice) product by Google. It lets you use authentication, storage, analytics and many other features in your apps without the implications of developing and maintaining your own backend. It works in Android/IOS apps, websites and even Unity games. Firebase comes with a completely free plan, so you can try it out without entering your credit card information.
One killer feature that Google Firebase has is that its database (Firebase Firestore) supports offline capabilities out-of-box. It is especially useful while you are developing PWA (Progressive Web Apps) and need to handle offline states. I will not go into it in this tutorial, but let me know if you would like to find out more.
Adding Firebase to your project
First of all, you have to have a Google account, which you can create here. Then, go to Firebase console and sign in with your newly created Google account. In Firebase console, you should see a big button saying Add project.
You will be asked for the project name, feel free to put whatever you like (I used todo-tutorial). The next step will be choosing whether to include Google Analytics for your project. I suggest leaving it off, as it is a complex topic on its own and I will cover it in a later tutorial. After this is done, you will be presented with this screen:
Proceed with clicking the android icon to add Firebase to your app. The next dialog will ask you for the Android package name. You created in the first part of this tutorial. If you do not remember it, you can find it in <project folder>/android/app/build.gradle
on line starting with applicationId
. For me (and for you if you got the code from github) it was org.michaelkrasnov.todo_app
. Enter something suitable in the App nickname and leave SHA-1 certificate empty. Firebase will put together a config file for you, which you have to download and put into <project folder>/android/app
:
Now we have to configure our app to use Firebase. Open file <project folder>/android/build.gradle
and add the following line in dependencies
:
classpath 'com.google.gms:google-services:4.3.3'
And ensure that google()
is present in allprojects -> repositories
and in buildscript -> repositories
. Now open file <project folder>/android/app/build.gradle
and add this line to the bottom of the file:
apply plugin: 'com.google.gms.google-services'
Now you need to add Firebase plugins to Flutter itself. Open <project folder>/pubspec.yaml
and edit it like this:
dependencies:
flutter:
sdk: flutter
# Add these lines:
firebase_core: ^0.4.0+9
firebase_auth: ^0.14.0+5
cloud_firestore: ^0.12.9+5
To install them, run flutter packages get in the command line, or press Packages get in Android Studio:
Now you should be ready to start writing code with Firebase.
Note: after adding Firebase dependencies, you may get this error when trying to run the app:
This is a bug in Firebase right now, and to fix it, change minSdkVersion
from 16 to 21 in <project folder>/android/app/build.gradle
Splitting screens in separate files
Right now, we have all the screens and widgets in one file, main.dart
. It is not a very good practice, so let us split it into two files: list.dart
and create.dart
:
As you can see, all we did is move the TODOList
class into list.dart
, TODOCreate
and TODOCreateState
into create.dart
and added relevant imports. Code now looks much cleaner, don’t you think?
Adding a login screen
First of all, we need to be able to authenticate people. To do this, we will need:
- Add a screen to log in
- Set up authentication on Firebase console
- Tie together our login screen and Firebase API
Let’s start by creating login.dart
file (alongside main.dart, list.dart
etc.) and defining the TODOLogin
widget in it. Then, add it to the routes in main.dart
. Here is the code in question:
You can see that we have defined a new widget TODOLogin
that has text fields for email and password, as well as the login button. Upon pressing the login button, it will send email and password to root widget via callback (line 58). The login screen looks like this:
We will now use this data to authenticate the user.
Authenticating with Firebase
To authenticate, we will create a new class Authentication
in auth.dart
, which will communicate with Firebase. TODOLogin
will call it on the login button press, pass the user to TODOState
and navigate to the next page.
If you try to log in now, you will see this:
That happens because there are no users in Firebase right now, and we did not implement signing up yet. Let’s add some users then!
Setting up authentication in Firebase Console
To enable authentication for your app, you need to go to Firebase Console. Select your project from the list and open the Authentication section. In it, press Set up sign-in method button:
In the next screen, select Email/Password and enable it:
As we do not have any sign-up functionality yet, you have to add yourself as a user to be able to log in. Open Users tab and set an email and password:
Now you should be able to login to your app.
Common issues with Authentication
After you have set up authentication, there are a number of bugs in Flutter and Firebase that may prevent it from working. One of the errors you may encounter is:
java.lang.IllegalStateException: FirebaseApp with name [DEFAULT] doesn't exist.
To fix it, add this line to your <project folder>/android/app/build.gradle
file:
apply plugin: 'com.google.gms.google-services'
If you get this error when trying to log in:
Caused by: java.lang.ClassNotFoundException: Didn't find class "android.support.v4.util.ArrayMap"
Then you need to add the following to the <project folder>/android/gradle.properties
file:
android.useAndroidX=true
android.enableJetifier=true
Saving tasks to Firebase Firestore
First, some insight into how Firestore works. This is a NoSQL, document-oriented database, very much similar to MongoDB. In your database, you have collections, which have documents in them. A document can have fields with data, as well as other collections.
First, you have to turn on Firestore in Firebase Console. Open it and go to the Database tab. Click Create Database and select Start in testing mode:
With the database in place, it is time to write some code. We will first change TODOList
to fetch data from Firestore and update it if the user completes the task:
Note that we removed all the callbacks and the tasks
list from TODOList
and update line in main.dart
like this:
'/list': (context) => TODOList(),
Now you should be able to see the list of the tasks on the screen. It is empty right now, so we need to hook the TODOCreate
to the Firestore. Change the code in create.dart
and main.dart
like this:
Now your app is fully functional. You can log in, add new tasks, complete them and store everything on the server. You can see these changes in real-time in Firebase Console. Of course, it is far from ideal: it does not support more than one user right now and UI could use some work.
What next?
I purposefully left the app with only the minimum bare functionality so that you can have a chance to practice on your own. Here are some suggestions for improvements to further boost your Flutter and Firebase skills:
- Polish the UI. Add icons, labels, colors, etc. Unleash your inner designer and hack away. You can refer to Flutter Widgets docs to find out what is available.
- Add validation. Right now, there is no validation on any of the text fields. You can check for email formatting, non-emptiness of password and task names and much more.
- Multi-user support. To do this, you need to come up with a way to categorize tasks by the user. The simplest way to do that is to add a
uid
property to the task and filter by it when getting the tasks. - Security. Our database is running in development mode. That means there are no security rules in place. Of course, it is not suitable for production. Refer to Firestore docs to learn how to set up security for your project.
This list is far from complete and I am sure you have more features in mind. Please share them in the comments, as well as the apps you have come up with, I would love to take a look. Thank you for reading and see you soon!