Flutter : How to do user login with Firebase

David Cheah
10 min readAug 25, 2018

--

Release

25/12/18 — Updated latest code snippet after refactoring and clean up.

24/01/19 — Duplicated link to github at the top of article.

23/07/19 — Added trim method to email and password value

13/10/19 — Updated code and article after cleanup and packages update

Source Code

In case you want to skip the whole mumbo jumbo, you can grab the source code here 👇

What is Flutter?

Flutter is an open source mobile SDK developed by Google to build high quality applications for Android and iOS. It allows developers to not only build application with beautiful design, smooth animation and fast performance, but also able to integrate new features quickly. Flutter offers high velocity development with its stateful hot reload and hot restart. With only one code base to manage, you get to save a lot of cost comparing to managing both Android and iOS projects as Flutter compiles it to native ARM code. Flutter uses Dart programming language which is also developed by Google.

Why Dart?

  • A terse, strongly-typed, object-oriented language.
  • Supports just-in-time (JIT) and ahead-of-time (AOT) compilation.
  • JIT allows Flutter to recompile code directly on the device while the app is still running.
  • Enable fast development and enables sub-second stable hot reloading.
  • AOT allows code to be compiled directly into native ARM code leading to fast startup and predictable performance.

What is Firebase

Firebase is a mobile and web development platform that provides developer with wide range of products. Today we will be looking on how we can build our first Flutter application with Firebase authentication and realtime database (RTDB). This application allows user to perform account sign up/login and CRUD actions on todo items with Firebase. On this post, we are going to solely focus on the user signup and login part.

What are the tools version?

Here are the versions of the tools used for this article and on Github

  • Flutter Version: flutter_macos_v1.9.1+hotfix.4-stable
  • Android Studio Version: 3.5
  • Xcode Version: 11.1

How to setup environment?

  • Follow through the instructions in https://flutter.dev/docs/get-started/install to get your Flutter SDK. You should be installing either Android Studio or VSCode with Flutter and Dart plugins.
  • Run Flutter doctor to install any dependencies
flutter doctor
  • To launch iOS simulator use the following command:
open -a Simulator
  • To launch android emulator, do the following: Launch Android Studio > tools > AVD Manager and select create Virtual Device.

Building Flutter App

You can get the complete source code in the GitHub link at the bottom of the post. The following shows how do we derive from Flutter sample project to complete source code in GitHub.

👉Step 1: Create a new Flutter project call flutter login demo

Launch simulator and run project using Flutter.

flutter run

If you have both Android emulator and iOS Simulator running, run the following command to execute on both.

flutter run -d all

You should see similar screens on both Android Emulator and iOS Simulator.

Left: Android, Right: iOS

If you’re interested to know how to get screenshots at your simulators;

For Android: Simply click the camera icon on the left side of the tool pane. The image will be saved to desktop

For iOS: [Option 1] Hold and press command + shift + 4. Press the space bar to change Mouse pointer to camera icon. Point to iOS simulator, click to take screenshot. The image will be saved to desktop.

For iOS: [Option 2] Select Simulator and press command + S. Thanks JerryZhou for sharing this information.

👉Step 2: Replace original code with Hello World

At main.dart, erase all contents and add the following boilerplate to your file. We are going to create a new file called login_signup_page.dart which has LoginSignupPage class. On your terminal, hit the R key to perform hot reload and you should see “Hello World” on the screen.

👉Step 3: Changing from stateless to stateful

👉Step 4: Replace Hello World with Stack

Inside the Scaffold body, let’s replace the Hello Word text widget to a stack widget. The stack widget allows us to overlay one widget above the other. The idea is to show circular progress bar when any login or sign up activity is running. In the stack, we will have the Form and the circular progress bar. Inside the Form, we will add a ListView that allows us to put an array of widgets. With that, we are able to refactor out several UI Components and put them inside the ListView.

Pro Tip : Whenever we are using text input, it is better to wrap it around a ListView to prevent rendering error when the soft keyboard shows up due to overflow pixels.

👉Step 5: Building each UI components

We start with building our circular progress bar. Thanks to Flutter, this is available as built-in widget call CircularProgressIndicator . We will use bool _isLoading to determine whether to show the CircularProgressIndicator or not.

Next, we will build our form logo. In the repository, there is an asset folder with the following flutter-icon.png file. To import into our project, add the following line in pubspec.yaml followed by flutter packages get command.

Next we will add 2 TextFormField for our email and password. TextFormField allows us to add validator and onSaved. These are callback methods will be triggered when form.validate() and form.save() is called (TextFormField is inside Form). So for example if form.save() is called, the value in the text form field is copied into another local variable.

For the validator, we will check for empty field inputs and update _errorMessage to be shown to user. We also need to create variables _email and _password to store the values which are then used for authentication later.

For password, we set obsecureText: true to hide user password.

We set maxLines to 1 to fix the height of the textformfield.

autofocus is set to false to prevent the field getting focused when the page is loaded.

Both email and password values are trim() to remove any whitespaces.

Next we will build 2 buttons. One primary for user action of login/signup and one secondary to switch form mode(between login and signup). For keeping track of the form mode, we use a boolean _isLoginForm

When the primary button press event is detected, we will trigger method _validateAndSubmit which validates our TextFormField inputs and perform login/signup.

Next we build secondary button to toggle between login and signup. We use FlatButton instead of RaisedButton used in primary button. The reason is if you have 2 buttons and would like to make 1 more distinctive than the other, RaisedButton is the right choice as it catches more attention compare to FlatButton .

For switching between login and signup, it is just doing set state with toggled bool _isLoginMode . Also we have a resetForm method to clear the inputs of the TextFormField (this is kept in GlobalKey of FormState which will be explained later below).

Next, we are build UI to display error messages to user. These messages could be error thrown by Firebase or invalid form input. If there is a new error message, we will setstate with new values of _errorMessage

Finally, back to our form, we need to create a GlobalKey to keep our form state. This keeps track of the user typed email and password.

Add the following in class _LoginSignupPageState

final _formKey = new GlobalKey<FormState>();

And linked it under _showForm(), key properties

Widget _showForm() {
return new Container(
padding: EdgeInsets.all(16.0),
child: new Form(
key: _formKey,
child: new ListView(
shrinkWrap: true,
children: <Widget>[
showLogo(),
showEmailInput(),
showPasswordInput(),
showPrimaryButton(),
showSecondaryButton(),
showErrorMessage(),
],
),
));
}

Let’s arrange those individual UI components and put it back to our ListView.

Let’s try to run our project using flutter run command

TextFormField validator in action

👉Step 6: Register project with Firebase

Go to https://console.firebase.google.com and register new project.

For android, click the android icon. Enter your package name which can be found in android/app/src/main/AndroidManifest.xml

Download the config file which is google-services.json (Android).

Drag the google-services.json into app folder in project view

We need to add the Google Services Gradle plugin to read google-services.json. In the /android/app/build.gradle add the following to the last line of the file.

apply plugin: 'com.google.gms.google-services'

In android/build.gradle, inside the buildscript tag, add new dependency.

buildscript {   repositories {      //...}dependencies {   //...   classpath 'com.google.gms:google-services:3.2.1'}

For iOS, open ios/Runner.xcworkspace to launch Xcode. The package name can be found in bundle identifier at Runner view.

Download the config file which is GoogleService-info.plist (iOS).

Drag the GoogleService-info.plist into the Runner subfolder inside Runner as shown below.

👉Step 7: Add dependencies in pubspec.yaml

Next we need to add firebase_auth and firebase_database dependency in pubspec.yaml. To get the latest version number, go to https://pub.dartlang.org/ and search for the dependency.

firebase_auth: ^0.14.0+5
firebase_database: ^3.0.7

👉Step 8: Import Firebase Auth

import 'package:firebase_auth/firebase_auth.dart';

👉Step 9: Enable sign up using email and password at Firebase

👉Step 10: Create abstract class BaseAuth

Create new file call authentication.dart. We are going to create abstract class BaseAuth. The class only contains the signature of the methods and this ensures minimal changes if we decided to change from Firebase to something else.

👉Step 11: Create class Auth implementing BaseAuth

This is where we define what the methods in the abstract class do.

👉Step 12: Create HomePage

Once user logs in successfully, they will be directed into home page.

👉Step 13: Create RootPage

So RootPage which is a stateful page, actually decides whether to show user LoginSignupPage or HomePage based on their authentication status.

Hence, we keep track of the authentication status using :

enum AuthStatus {
NOT_DETERMINED,
NOT_LOGGED_IN,
LOGGED_IN,
}

When the RootPage is loaded, we will try to get the userid and set AuthStatus

We have 2 callback functions for login and logout. So when user is at LoginSignupPage and successfully logs in, it will trigger the login callback in RootPage, that sets the AuthStatus to LOGGED_IN and subsequently show user the HomePage.

The same happens when user successfully logs out when in HomePage.

Here is the part, on showing user the correct page according to their AuthStatus. More explaination on auth in the subsequent steps.

👉Step 14: Initialize Auth in main

In main.dart, when we call new RootPage, we initialize new Auth() and pass into RootPage as shown.

In root_page.dart, we receive the initialized auth as follows:

👉Step 15: Link up LoginSignupPage and RootPage

In the RootPage class, we call LoginSignupPage and pass in auth that was pass in earlier from main.dart and also linked our callback function. We use widget.auth instead of auth is because this variable was pass into rootpage class from MyApp class instead of initialized in rootpage.

Here is snapshot of code inside RootPage class that calls LoginSignupPage

Here is snapshot of code inside LoginSignupPage that receives the auth and loginCallback

👉Step 16: Use Auth inside LoginSignupPage

We use widget.auth.signIn that was implemented according to abstract class BaseAuth to log user in. The underlying method uses Firebase signInWithEmailAndPassword which returns a future value. A future is part of asynchronous operation that does not block the main thread. Future class is associated with async and await keyword. Hence the method needs to have await and the external wrapper function needs to have async. So we enclose the login and signup methods with try catch block. If there was an error, our catch block should be able to capture the error message and display to the user.

👉Step 17: Try to sign up a user

Let’s try to sign up a user by entering an email and password.

If you encounter something like below, this is because there is an extra spacing at the end of your email. That is why I have added trim to email

I/flutter (14294): Error PlatformException(exception, The email address is badly formatted., null)

If you encounter something like below, change your password to be at least 6 characters long.

I/flutter (14294): Error PlatformException(exception, The given password is invalid. [ Password should be at least 6 characters ], null)

Finally once success, you should be able to see in your terminal the following line. The random string is the user ID.

I/flutter (14294): Signed up JSwpKsCFxPZHEqeuIO4axCsmWuP2

Similarly if we try to sign in the same user we signed up, we should get something like this:

I/flutter (14294): Signed in JSwpKsCFxPZHEqeuIO4axCsmWuP2
Debug banner removed

ProTip: Notice the debug ribbon at the top right corner of the app, you can easily remove it by adding the following line inside MaterialApp widget in main.dart

debugShowCheckedModeBanner: false,

🎊🎉Success🎉🎊

Demo login screen

Source Code

You can get the complete source code in the github link below

Followup article

Here is a sequel to this post which is How to do CRUD with Firebase RTDB. Check it out!

If you find this article is useful, give some 👏

Reference

The Flutter Pub is a medium publication to bring you the latest and amazing resources such as articles, videos, codes, podcasts etc. about this great technology to teach you how to build beautiful apps with it. You can find us on Facebook, Twitter, and Medium or learn more about us here. We’d love to connect! And if you are a writer interested in writing for us, then you can do so through these guidelines.

--

--