Phone Authentication with Flutter and Firebase

Phone Authentication with Flutter and Firebase

In this video tutorial, we will learn how to setup/create Firebase Phone Authentication with Flutter and Learn how to add firebase to a flutter project. Add phone authentication to flutter project.

In this video tutorial, we will learn how to setup Firebase Phone Authentication with Flutter and Learn how to add firebase to a flutter project. Add phone authentication to flutter project.

Source Code: https://github.com/HathTech/flutter-firebase-tutorials

Implementing Firebase GitHub Authentication in Flutter

Implementing Firebase GitHub Authentication in Flutter

In this article, I’m going to show you how to implement Firebase&nbsp;<strong>GitHub authentication</strong>&nbsp;method properly in Flutter. As you might already know, there are some&nbsp;<a href="https://github.com/flutter/plugins" target="_blank">Flutter plugins</a>&nbsp;that are provided officially by Flutter team. And&nbsp;<a href="https://github.com/flutter/plugins/blob/master/FlutterFire.md" target="_blank"><strong>FlutterFire</strong></a>&nbsp;is a subset of them. I encourage you to go and try FlutterFire plugins sometime if you need quick backend solutions for your small-scale project; but for this article we’re going to need only the&nbsp;<a href="https://pub.dartlang.org/packages/firebase_auth" target="_blank"><strong>firebase_auth</strong></a>&nbsp;plugin. So, let me list all the steps that you need to do in order to implement GitHub authentication in your Flutter app:

In this article, I’m going to show you how to implement Firebase GitHub authentication method properly in Flutter. As you might already know, there are some Flutter plugins that are provided officially by Flutter team. And FlutterFire is a subset of them. I encourage you to go and try FlutterFire plugins sometime if you need quick backend solutions for your small-scale project; but for this article we’re going to need only the firebase_auth plugin. So, let me list all the steps that you need to do in order to implement GitHub authentication in your Flutter app:

Step 0. First of all, I assume that you already downloaded google-services.jsonfile for Android and GoogleServices-Info.plist file for iOS from Firebase dashboard of your app and added them to your project properly.

Step 1. Create an OAuth app on GitHub via this link: https://github.com/settings/applications/new. As “authorization callback URL”, you need to set something like appname://auth. You will be redirected to this URL once you successfully log into GitHub on browser (which is the first step of this authorization method from the user point of view). We’re going to utilize this URL in later steps to set app’s deep link configuration, so that you can be redirected back to the app.

Step 2. Enable GitHub authentication method on your app’s Firebase dashboard. As “client ID” and “client secret”, you need to set what GitHub has provided after creating OAuth app on GitHub in Step 1.

Step 3. Add the following dependencies to your project’s pubspec.yaml file:

dependencies:
  firebase_auth: ^0.8.2
  google_sign_in: ^4.0.1+1
  url_launcher: ^5.0.2
  uni_links: ^0.2.0
  http: ^0.12.0+1
  • google_sign_in is required by the firebase_auth dependency, so we need to add it as well.
  • url_launcher will be used to launch URL on browser.
  • uni_links is to add deep-linking capability to the app.
  • http will be used to send HTTP request.

Why and where we need all these will be much more meaningful in the upcoming steps, so please bear with me.

Step 4. For Android, add the following classpath to project-level build.gradlefile located under /android directory:

dependencies {
  // ...
  classpath 'com.google.gms:google-services:4.2.0'
}

And add the following line to the end of app-level build.gradle file located under /android/app directory:

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

Finally, add the following intent filter to the AndroidManifest.xml file located under /android/app/src/main directory:

<intent-filter>
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data
        android:host="auth"
        android:scheme="appname" />
</intent-filter>

Please be aware that scheme and host properties we set here are from Step 1.

Step 5. For iOS, add the following attributes into Info.plist file located under /ios/Runner directory:

<key>CFBundleURLTypes</key>
<array>
   <dict>
        <key>CFBundleTypeRole</key>
        <string>Editor</string>
        <key>CFBundleURLSchemes</key>
        <array>
            <string><!-- REVERSED_CLIENT_ID from GoogleServices-Info.plist --></string>
        </array>
   </dict>
   <dict>
        <key>CFBundleTypeRole</key>
        <string>Editor</string>
        <key>CFBundleURLName</key>
        <string>auth</string>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>foreach</string>
        </array>
    </dict>
</array>

Once again, please be aware that in the first “dict” element of the above array, value of CFBundleURLSchemes attribute is set as the REVERSED_CLIENT_IDwhich you can find in GoogleServices-Info.plist file. Similarly, values of CFBundleURLName and CFBundleURLSchemes attributes in the second “dict” element are from Step 1.

We’ve completed the configuration steps. Now, we are ready to go with coding…

Step 6. Import url_launcher package and launch the GitHub login page on browser as follows:

import 'package:url_launcher/url_launcher.dart';
//...
void onClickGitHubLoginButton() async {
  const String url = "https://github.com/login/oauth/authorize" +
      "?client_id=" + GITHUB_CLIENT_ID +
      "&scope=public_repo%20read:user%20user:email";

if (await canLaunch(url)) {
await launch(
url,
forceSafariVC: false,
forceWebView: false,
);
} else {
print("CANNOT LAUNCH THIS URL!");
}
}

On this page, you need to log in with your GitHub credentials.

Step 7. After successfully logging into GitHub, you will get redirected to the URL appname://auth?code=AUTH_CODE. This redirection will be caught by your app as we’ve set the deep link configuration in Step 4 & 5, and you will be redirected (or asked to be redirected) back to the app afterwards.

At this point, to be able to handle incoming data, or in other words, to parse the URL given above and get the AUTH_CODE value from it, you need to have the following setup in your login page widget code:

import 'package:flutter/material.dart';
import 'dart:async';
import 'package:uni_links/uni_links.dart';

class LoginPage extends StatefulWidget {
@override
_LoginPageState createState() => _LoginPageState();
}

class _LoginPageState extends State<SplashPage> {
StreamSubscription _subs;

// ...

@override
void initState() {
_initDeepLinkListener()
super.initState();
}

@override
void dispose() {
_disposeDeepLinkListener();
super.dispose();
}

// ...

void _initDeepLinkListener() async {
_subs = getLinksStream().listen((String link) {
_checkDeepLink(link);
}, cancelOnError: true);
}

void _checkDeepLink(String link) {
if (link != null) {
String code = link.substring(link.indexOf(RegExp('code=')) + 5);
loginWithGitHub(code)
.then((firebaseUser) {
print("LOGGED IN AS: " + firebaseUser.displayName);
}).catchError((e) {
print("LOGIN ERROR: " + e.toString());
});
}
}

void _disposeDeepLinkListener() {
if (_subs != null) {
_subs.cancel();
_subs = null;
}
}
}

In this code, basically we are listening for deep links incoming to the app.

As stated, after a successful GitHub login, you have the “code” in your hands. Now, you need to use it to get access token from GitHub, which afterwards will be used to log into Firebase. Remember that this is our eventual goal.

Step 8. Implement the loginWithGitHub(…) method highlighted in the code above as follows:

import 'package:firebase_auth/firebase_auth.dart';
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
// ...
Future<FirebaseUser> loginWithGitHub(String code) async {
//ACCESS TOKEN REQUEST
final response = await http.post(
"https://github.com/login/oauth/access_token",
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
},
body: jsonEncode(GitHubLoginRequest(
clientId: GITHUB_CLIENT_ID,
clientSecret: GITHUB_CLIENT_SECRET,
code: code,
)),
);

GitHubLoginResponse loginResponse =
GitHubLoginResponse.fromJson(json.decode(response.body));

//FIREBASE STUFF
final AuthCredential credential = GithubAuthProvider.getCredential(
token: loginResponse.accessToken,
);

final FirebaseUser user = await FirebaseAuth.instance.signInWithCredential(credential);
return user;
}
// ...
//GITHUB REQUEST-RESPONSE MODELS
class GitHubLoginRequest {
String clientId;
String clientSecret;
String code;

GitHubLoginRequest({this.clientId, this.clientSecret, this.code});

dynamic toJson() => {
"client_id": clientId,
"client_secret": clientSecret,
"code": code,
};
}

class GitHubLoginResponse {
String accessToken;
String tokenType;
String scope;

GitHubLoginResponse({this.accessToken, this.tokenType, this.scope});

factory GitHubLoginResponse.fromJson(Map<String, dynamic> json) =>
GitHubLoginResponse(
accessToken: json["access_token"],
tokenType: json["token_type"],
scope: json["scope"],
);
}

So, what we’re doing here is that first we request for an access token by sending an HTTP POST call (to the address shown in the code) whose request body contains the “code” we’ve got in the previous step, together with app’s GitHub credentials (“client ID” and “client secret”). Then, using the access token we get in the response of this call, we sign into Firebase. If everything goes successfully, you should eventually have FirebaseUser object in your hands.

Well done! You’re now logged into Firebase with your GitHub account.

I wanted to share how I implemented Firebase GitHub authentication in Flutter since I couldn’t find any resource on the net while trying to achieve this for my project. I hope that this article helps you and makes things clearer a bit.

Stay safe and with Flutter…

Originally published on https://medium.com

Firebase User Authentication in Flutter

Firebase User Authentication in Flutter

Flutter + Firebase Admin SDK: Adding Users; Updating Passwords. Each time you add a new user as the admin, you're logged out and the new user is logged in. Also, there is no way to update the user's password on the client.

Hello everyone, imagine that you have developed an enterprise-level Flutter application(client app) using firebase as the backend, and you are responsible for adding, updating and deleting users.

You will quickly realized that doing these from the client app has its limitations.

  • Each time you add a new user as the admin, you're logged out and the new user is logged in. You’ll then need to logout as the newly created user and re-login using your admin credentials; repeating this process to add a new user gets frustrating pretty quickly.
  • Also, there is no way to update the user’s password on the client

In this post, I will present to you with my solution for fixing these issue using the Firebase Admin SDK.

We’ll be writing some JavaScript, well Typescript, and deploying to Firebase. Don’t worry if you don’t know JavaScript/Typescript it’s quite similar to Dart and I will explain the code as we go along.

Firebase Config:

Ensure Email/Password Sign in is enabled in Firebase

Set Up a database structure like below

  • users: Once the user is registered they will show in this collection
  • users_registrations: when adding new users we submit to this collection if the user is successfully registered a document will be created in the “users” collection
  • user_update_password: whenever a document is added here it will trigger a cloud function to update the user's password
Code Time Flutter

This project uses firebase streams and the provider package to manage state; the moment a document is updated in Firestore it will be reflected on the client/Flutter app once the user has internet connection.

Also, the app has options for dark and light themes; 😀 see the post Flutter Light and Dark Theme to implement in your app.

We won't dive into the Flutter/Dart code in-depth because the focus is the firebase aspect, however, below are images of the folder structure and the final UI. You can get the full source code at Github Link

  • Folder Structure

Dark Theme


Light Theme

Code Time JavaScript/TypeScript

Ensure you have Node.js installed. Login to firebase using your credentials with the commands below:

npm install -g firebase-tools
npm install -g firebase-tools
firebase login
  • Create a new folder “firebase_credentials_cloud_functions”

Run the command, firebase init from that folder select functions at the prompt then select the corresponding project. Lastly, choose TypeScript as the language.

  • Create the folder structure below with the corresponding files.

  • onCreateUser.ts: whenever we add a users using our Flutter app to the collection “users_registrations” this function will fire and create the user using auth and add the document to ‘users’

  • onCreatePassword.ts: whenever we update a user’s password using our flutter app, a document will be written to the collection “users_update_password” this function will fire and update the user’s password.

  • Time to deploy our code: from the console/terminal run firebase deploy — only functions
We’re done!

Conclusion

Flutter and Firebase are the perfect duo for rapid mobile development; using both helps you the bring more value to your users while reducing lengthy configurations.

JavaScript/TypeScript is easy to learn/understand if you already know Dart.

Maybe in the future, Google will give us the ability to write cloud functions using dart. 😀

If this helped: please give me a clap, and star the GitHub repo, it keeps me motivated 😀

⭐ Source Code Flutter: GitHub Link

⭐ Source Code Firebase/JavaScript: GitHub Link

Simple Login Flow With Flutter and Firebase

Simple Login Flow With Flutter and  Firebase

Simple Login Flow With Flutter and Firebase .In this article we implement a simple authentication flow in Flutter, in less than 100 lines of code. After creating a new Flutter project

In this piece, we will create a simple application with the following components:

  • Default Main App Entry Point
  • Use of FutureBuilder Widget to wait for data before rendering UI, the concept used throughout the app
  • Login Page
  • Home Page
  • Authentication Service
  • Demonstrate the use of the Provider as discussed in the Flutter Documentation Simple App State Management

There are plenty of examples online about setting up Firebase for Flutter so I will jump right into the code instead of walking thru the basics.

See Google CodeLabs Flutter for Firebase for step-by-step instructions for setting up you project on iOS or Android

Create a Test User in Firebase

Since we are just building the application and there is no functionality to create users right now, log in to you Firebase Console and add a user to your project. Be sure to enable email authentication when updating the project in your Firebase Console.

Cleaning Up the Default Flutter Project

First, let's create the project:

flutter create simple_firebase_auth

Now let's do some project cleanup, open up the project and delete the existing HomePage and HomePageState widget from the file main.dart.

Change the home property of the MaterialApp widget to point to the LoginPage widget we are about to create in the next section.

The file should look similar to this when completed:

import 'package:flutter/material.dart';
import 'package:simple_firebase_auth/login_page.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: LoginPage(),
    );
  }
}

Markdium-javascript.js

Create the Login Page Widget

Let's walk through the creation of the LoginPage for the application. We need to capture an email and a password to pass to the AuthService to call the login function.

We are going to create a simple page with the required TextFormField widgets and one RaisedButton that will log a user in when clicked.

  1. Open your editor and create a new file in the lib directory named login_page.dart
  2. Paste the contents below into the file login_page.dart
import 'package:flutter/material.dart';

class LoginPage extends StatefulWidget {
  @override
  _LoginPageState createState() => _LoginPageState();
}

class _LoginPageState extends State<LoginPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Login Page Flutter Firebase"),
      ),
      body: Center(
        child: Text('Login Page Flutter Firebase  Content'),
      ),
    );
  }
}

Markdium-javascript.js

You should be able to run the code to see what the screen looks like now. Be sure to change the default route or home property in main.dart widget to LoginPage while we work through the UI so you can see the changes update live.

Style and Adding Text Fields

Let's make the body of the page a centered Column with the children of the column being primarily the TextFormField and the RaisedButton.

The centered container to hold the form fields and buttons:

 body: Container(
      padding: EdgeInsets.all(20.0),
      child: Column()
    )

Markdium-javascript.js

Next, add the actual form field widgets and the buttons as children of the Column widget. We will do some basic styling of the form fields so that this looks presentable. See the Flutter documentation for more information on TextFormFields.

 body: Container(
    padding: EdgeInsets.all(20.0),
    child: Column(
      children: <Widget>[
        Text(
          'Login Information',
          style: TextStyle(fontSize: 20),
        ),
        TextFormField(
            keyboardType: TextInputType.emailAddress,
            decoration: InputDecoration(labelText: "Email Address")),
        TextFormField(
            obscureText: true,
            decoration: InputDecoration(labelText: "Password")),
        RaisedButton(child: Text("LOGIN"), onPressed: () {}),
      ],
    ),
  ),

Markdium-javascript.js

Let’s add some spacing between the fields in the column so it is more presentable. We are going to use the SizedBox widget and set the height property to get some spacing in the application. Replace the children property of the Column widget to get the desired spacing.

children: <Widget>[
    SizedBox(height: 20.0),    // <= NEW
    Text(
      'Login Information',
      style: TextStyle(fontSize: 20),
    ),
    SizedBox(height: 20.0),   // <= NEW
    TextFormField(
        keyboardType: TextInputType.emailAddress,
        decoration: InputDecoration(labelText: "Email Address")),
    TextFormField(
        obscureText: true,
        decoration: InputDecoration(labelText: "Password")),
    SizedBox(height: 20.0),  // <= NEW
    RaisedButton(child: Text("LOGIN"), onPressed: () {}),
  ],

Markdium-javascript.js

Getting Text Values from Form Fields

We are going to be using a Form widget and a GlobalKey, additional information on these concepts can be found in the flutter cookbook section Building a form with validation.

Add the formKey in the LoginPage widget:

class _LoginPageState extends State<LoginPage> {
  final _formKey = GlobalKey<FormState>();

Markdium-javascript.js

Then add two new fields to hold the email address and password values we will need to send to Firebase for authentication:

class _LoginPageState extends State<LoginPage> {
  final _formKey = GlobalKey<FormState>();
  String _password;
  String _email;

Markdium-javascript.js

Next, add a property onSaved to the TextFormFields we have for email and password. When the save method is called on the form, all of the widgets onSaved methods will be called to update the local variables.

TextFormField(
      onSaved: (value) => _email = value,    // <= NEW
      keyboardType: TextInputType.emailAddress,
      decoration: InputDecoration(labelText: "Email Address")),
  TextFormField(
      onSaved: (value) => _password = value, // <= NEW
      obscureText: true,
      decoration: InputDecoration(labelText: "Password")),

Markdium-javascript.js

Wrap the Column widget with a new Form widget, the code should look similar to this:

body: Container(
        padding: EdgeInsets.all(20.0),
        child: Form(          // <= NEW
          key: _formKey,      // <= NEW
          child: Column(
            children: <Widget>[
            ....
            ],
          ),
        ),
      ),      

Markdium-javascript.js

Now that the fields are set and the TextFormField are updated, we can use the _formKey to not only validate the fields provided but also retrieve the values locally by calling the save method.

Replace the code in the RaisedButton onPressed method to the following, and you will see that we are getting the values for email and password set in out widget. We can now pass these values to the AuthService that wraps the Firebase sign-in functionality.

 // save the fields..
    final form = _formKey.currentState;
    form.save();

    // Validate will return true if is valid, or false if invalid.
    if (form.validate()) {
      print("$_email $_password");
    }

Markdium-javascript.js

Create the Home Page Widget

For now, we will keep the home page simple, since our goal is to demonstrate how the flow works. Ignore the commented out LogoutButton widget, we will discuss that in a later section of the tutorial.

  1. Open your editor and create a new file in the lib directory named home_page.dart
  2. Paste the contents below into the file home_page.dart
import 'package:flutter/material.dart';

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    return  Scaffold(
      appBar: AppBar(
        title: Text("Home Flutter Firebase"),
        //actions: <Widget>[LogoutButton()],
      ),
      body: Center(
        child: Text('Home Page Flutter Firebase  Content'),
      ),
    );
  }
}

Markdium-javascript.js

  1. Open main.dart and add the following import statement
import 'home_page.dart';

Markdium-javascript.js

  1. To verify that the page is working properly, change the home property from this:
home: HomePage(title: 'Flutter Demo Home Page'),

Markdium-javascript.js

To this:

home: HomePage(),

Markdium-javascript.js

Creating a Template for an Authentication Service

Here we will build out the authentication service separate from Firebase, validate that everything works and then integrate Firebase.

In this service, we are using a mixin called ChangeNotifier and a method notifyListeners which will allow for the widgets that are using this Service to be updated when the method is called. We are calling notifyListeners when we update the currentUser property because that means that the user has either logged in or logged out and we want the application to update based on the user's state.

More information on Provider and State Management can be found here in the Flutter Documentation

What we need as a baseline is the following:

import 'dart:async';
import 'package:flutter/material.dart';

class AuthService with ChangeNotifier {
  var currentUser;

  AuthService() {
    print("new AuthService");
  }

  Future getUser() {
    return Future.value(currentUser);
  }

  // wrappinhg the firebase calls
  Future logout() {
    this.currentUser = null;
    notifyListeners();
    return Future.value(currentUser);
  }

  // wrapping the firebase calls
  Future createUser(
      {String firstName,
      String lastName,
      String email,
      String password}) async {}

  // logs in the user if password matches
  Future loginUser({String email, String password}) {
    if (password == 'password123') {
      this.currentUser = {'email': email};
      notifyListeners();
      return Future.value(currentUser);
    } else {
      this.currentUser = null;
      return Future.value(null);
    }
  }
}

Markdium-javascript.js

We will keep a local property in the service call currentUser which is the object storing the user when the user calls the login method and if the password matches we will set currentUser and the user will be logged in. This will now provide a user when the call is made to getUser method. For logging the user out, we will set the currentUser property to null indicating that we are no longer logged into the system.

Determining User State On Application Launch

The first challenge when working with the application is to determine which page to open when the application starts up. What we want to do here is determine if we have a user or not. We will be using an AuthService we created above combined with the FutureBuilder widget from Flutter to render the correct first page of either a HomePage or a LoginPage.

Using the Provider

In main.dart we will need to update the default main method to look like this; we are wrapping the whole application with the ChangeNotifierProvider to get the ability to scan up the widget tree and find an object of type AuthService.

void main() => runApp(
      ChangeNotifierProvider<AuthService>(
        child: MyApp(),
        builder: (BuildContext context) {
          return AuthService();
        },
      ),
    );

Markdium-javascript.js

Modifying the MyApp Widget

Go into the main.dart and make the following changes that will allow the MyApp widget to set the route. This widget will determine if the application should navigate to the HomePage widget or LoginPage widget when the app is launched.

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: FutureBuilder(
        // get the Provider, and call the getUser method
        future: Provider.of<AuthService>(context).getUser(),
        // wait for the future to resolve and render the appropriate
        // widget for HomePage or LoginPage
        builder: (context, AsyncSnapshot snapshot) {
          if (snapshot.connectionState == ConnectionState.done) {
            return snapshot.hasData ? HomePage() : LoginPage();
          } else {
            return Container(color: Colors.white);
          }
        },
      ),
    );
  }
}

Markdium-javascript.js

Modifying the Login Page Widget

Now that the AuthService can be accessed using the Provider, we can call the login function when the used clicks the button. Open the file login_page.dart and find the onPressed method for the login button and make the following change

  // Validate will return true if is valid, or false if invalid.
    if (form.validate()) {
      var result = await Provider.of(context)
          .loginUser(email: _email, password: _password);
      if (result == null) {
        // see project in github for this code
        //return _buildShowErrorDialog(context,
        //    "Error Logging In With Those Credentials");
      }
    }

Markdium-Dart.dart

We are using the Provider.of method to look up the widget tree and get our AuthService and then we have access to all of the methods, specifically the loginUser method.

Conclusion

At this point, you should have a functioning application with the basic login flow where the user will be logged in successfully if you provide the password as password123.

I added in a little extra functionality of displaying an error message in a dialog box if some sort of error occurs.

Now we will integrate firebase into the application.

There are plenty of examples online about setting up Firebase for Flutter, so I will jump right into the code instead of walking through the basics. See Google CodeLabs Flutter for Firebase for step-by-step instructions for setting up your project on iOS or Android.

Create a Test User in Firebase

Since we are just building the application, and there is no functionality to create users in the application right now, please log in to your Firebase Console and add a user to your project. Please be sure to enable email authentication when updating the project in your Firebase Console.

Steps For Adding Firebase Functionality to the Project
  • Add the Firebase methods to the AuthService
  • Access the getUser property from the AuthService at startup to determine which page to load in main.dart
  • Modify HomePage to show email address of the logged in FirebaseUser
  • Modify LoginPage to call the loginUser method on the AuthService to login a user using the Firebase API to see if we can log in a real FirebaseUser
  • Finally, handle the errors appropriately when logging in and when looking for a current user at startup.
Authentication Service: Adding Firebase API Functionality

First the authentication service, which is where we are just wrapping some of the basic Firebase functions that we need for authentication and determining if there is already a user persisted from a previous session.

import 'package:firebase_auth/firebase_auth.dart';
import 'dart:async';

import 'package:flutter/cupertino.dart';

class AuthService with ChangeNotifier {
  final FirebaseAuth _auth = FirebaseAuth.instance;

  ///
  /// return the Future with firebase user object FirebaseUser if one exists
  ///
  Future getUser() {
    return _auth.currentUser();
  }

  // wrapping the firebase calls
  Future logout() async {
    var result = FirebaseAuth.instance.signOut();
    notifyListeners();
    return result;
  }
  
  ///
  /// wrapping the firebase call to signInWithEmailAndPassword
  /// `email` String
  /// `password` String
  ///
  Future loginUser({String email, String password}) async {
    try {
      var result = await FirebaseAuth.instance
          .signInWithEmailAndPassword(email: email, password: password);
      // since something changed, let's notify the listeners...
      notifyListeners();
      return result;
    }  catch (e) {
      // throw the Firebase AuthException that we caught
      throw new AuthException(e.code, e.message);
    }
  }
}

Markdium-Dart.dart

As you can see from the code above, we still have the same methods for accessing our AuthService; the only difference now is that we have replaced the call with real calls to the Firebase back end that you have set up.

Notice we no longer need to keep a property with the current user since Firebase will manage that for us. All we need to do is call the method getUser, and if there is a user we will get an object. Otherwise it will return null.

Most important to notice is that we are calling notifyListeners() when the login state is changing during logging in or logging out.

Modifying main.dart

There are no real modifications needed to the file since we are working with the same external API. The only difference is that now we are returning a FirebaseUser object, so let’s add a specific type to the code, and touch up a few more things.

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: FutureBuilder(
        future: Provider.of(context).getUser(),
        builder: (context, AsyncSnapshot snapshot) { //          ⇐ NEW
          if (snapshot.connectionState == ConnectionState.done) {
            // log error to console                                            ⇐ NEW
            if (snapshot.error != null) {
              print("error");
              return Text(snapshot.error.toString());
            }
            // redirect to the proper page, pass the user into the 
            // `HomePage` so we can display the user email in welcome msg     ⇐ NEW
            return snapshot.hasData ? HomePage(snapshot.data) : LoginPage();
          } else {
            // show loading indicator                                         ⇐ NEW
            return LoadingCircle();
          }
        },
      ),
    );
  }
}

Markdium-Dart.dart

We have added the object type, FirebaseUser, associated with the AsyncSnapshot, and we are now checking for an error in case there is a problem loading Firebase initially.

We have also added a new parameter to the constructor of the HomePage widget, which is the FirebaseUser object returned from getUser call made to the AuthService. We will see in the next section how the new parameter is used.

Finally, we added a new widget called LoadingCircle to give us a nice user experience when the application is starting up and accessing Firebase to check for a new user. See the code below for the LoadingCircle widget.

See documentation on CircularProgressIndicator

class LoadingCircle extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        child: CircularProgressIndicator(),
        alignment: Alignment(0.0, 0.0),
      ),
    );
  }
}

Markdium-Dart.dart

Modifying HomePage Widget in home_page.dart

We need to first modify the widget by adding a new constructor that will hold the Firebase user passed in from the FutureBuilder in main.dart

class HomePage extends StatefulWidget {
  final FirebaseUser currentUser;    // ⇐ NEW

  HomePage(this.currentUser);        // ⇐ NEW

  @override
  _HomePageState createState() => _HomePageState();
}

Markdium-Dart.dart

Now we have access to the information on the current user from the widget; we can access it when rendering the HomePage by make the modifications you see below. We will just add a few more widgets to the build method:

children: [
       SizedBox(height: 20.0),                         // ⇐ NEW
       Text(                                           // ⇐ NEW
         'Home Page Flutter Firebase  Content',
         style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
       ),
       SizedBox(height: 20.0),                         // ⇐ NEW
       Text(                                           // ⇐ NEW
          `Welcome ${widget.currentUser.email}`,
           style: TextStyle(    
              fontSize: 18,
              fontWeight: FontWeight.bold,
              fontStyle: FontStyle.italic),
        ),
       SizedBox(height: 20.0),
       RaisedButton(
           child: Text("LOGOUT"),
           onPressed: () async {
             await Provider.of(context).logout();
           })
     ],

Markdium-Dart.dart

Modifying LoginPage Widget in login_page.dart

Since the API signature hasn’t changed, we need to do very little to this function to get the desired results. However, it would be best to do some better error checking.

With Future we need to wrap the call with a try catch block since any errors that happen with Firebase will be thrown as exceptions. We then will display the error message in a dialog. See code for the method _buildErrorDialog and the rest of the changes below.

Add the new import for the error exception:

import  'package:firebase_auth/firebase_auth.dart';

Markdium-Dart.dart

Make the appropriate changes to the onPressed method of the login button.

onPressed: () async {
       // save the fields..
       final form = _formKey.currentState;
       form.save();

       // Validate will return true if is valid, or false if invalid.
       if (form.validate()) {
         try {
           FirebaseUser result =
               await Provider.of(context).loginUser(
                   email: _email, password: _password);
           print(result);  
         } on AuthException catch (error) {
           // handle the firebase specific error
           return _buildErrorDialog(context, error.message);
         } on Exception catch (error) {
           // gracefully handle anything else that might happen..        
           return _buildErrorDialog(context, error.toString());
         }
       }
     },

Markdium-Dart.dart

Add the code for the new private _buildErrorDialog method that will display errors from the call to the AuthService login method.

 Future _buildErrorDialog(BuildContext context, _message) {
    return showDialog(
      builder: (context) {
        return AlertDialog(
          title: Text('Error Message'),
          content: Text(_message),
          actions: [
            FlatButton(
                child: Text('Cancel'),
                onPressed: () {
                  Navigator.of(context).pop();
                })
          ],
        );
      },
      context: context,
    );
  }

Markdium-Dart.dart

Conclusion

At this point, you should have a functioning application with the basic login flow where the user will be logged into Firebase using the credential for the test user you added in the Firebase Console.

Try entering invalid credentials for the password, and incomplete email addresses, and the errors should be displayed appropriately.