Developing a TODO app with Flutter – Part 3

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?

firebase logo

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.

screenshot of an add project button on google firebase

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:

screenshot of google firebase project home

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:

list of project files with google-services.json file highlighted

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:

screenshot of android studio with packages get button hightlighted

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:

error: the number of method references in a .dex file cannot exceed 64k

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:

  1. Add a screen to log in
  2. Set up authentication on Firebase console
  3. 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:

screenshot of the login screen with email and password entered

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:

screenshot with a login error

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:

screenshot of firebase console offering to setup a sign in method

In the next screen, select Email/Password and enable it:

screenshot of firebase email/password authentication provider settings. "Enable" button is hightlighted

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:

screenshot of new user form with a sample 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:

screenshot of security prompt while creating firestore database. test mode is selected

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:

  1. 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.
  2. 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.
  3. 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.
  4. 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!

Get new content delivered to your mailbox:

leave a comment