How to Login Facebook in Flutter

How to Login Facebook in Flutter

In this post, we will learn how to implement a Facebook Login using Google Firebase Authentication in Flutter based applications.

Introduction

In this post, we will learn how to implement a Facebook Login using Google Firebase Authentication in Flutter based applications.

Create a new Flutter Application

The first and basic step is to create a new application in Flutter. If you are a beginner in Flutter, you can check my blog Create a first app in Flutter. I have created an app named as “flutter_fb_login” here.

Create a Facebook App in Facebook Developer Console

For Facebook Login in Flutter, you need to create a Facebook app in the Facebook Developer Console. For that, follow the below steps.

  1. Go here and login using your Facebook account or create a new one.
  2. Then, create a new app using MyApp => Create App, give an app name and create the app. Check out the below screenshot.

  1. After creating the app, click on "Integrate Facebook Login" and Confirm.
  2. Go to the Dashboard tab. Under "My Products", you will find Facebook Login=>Settings (click on that).
  3. Then, on the left side, you will find QuickStart under Facebook Login menu. Just click on it.
  4. Select Android.

  1. On the next two screens, just click "Next". After that, you will get a screen asking for your Android project. Give the package name and default Activity Class name as I have given in the below screenshot. And go to Save => Use This Package => Continue.

Package Name: com.example.flutter_fb_login
Default Activity: com.example.flutter_fb_login.MainActivity
  1. On the next screen, you need to add your development and release key hashes. Follow Mac or Windows as per your OS and enter the hash key. Please note that the default Android Keystore password is "android". Copy the hash key and paste there and go to Save => Continue.
  2. Now, click "Next".
  3. Next, you need to put a lot of stuff in your app.

10.1. Go to your project's android/app/src/main/res/values folder. Under the Values folder, create a new file named strings.xml and put the below string (you have in your Facebook project ) in this file.

<?xml version="1.0" encoding="utf-8"?>  
<resources>  
   <string name="app_name">flutter_login</string>  
   <string name="facebook_app_id">2432431656989926</string>  
   <string name="fb_login_protocol_scheme">fb2432431656989926</string>  
</resources>  

10.2. Then, go to android/app/src/main/AndroidManifest.xml and paste the below code (you have in your Facebook project) under first activity and in the application tag. Please check the screenshot for better undestanding.

10.3. Click "Next" for all further screens.
10.4. You are all done with the Facebook App Setup. Congratulations…. :)

Set up a project in Google Firebase

Now, you need to set up a project in Google Firebase. Follow the below steps for that. Please follow the steps very carefully.

  1. Go here and add a new project. I will share the screenshot of how it looks so you will get a better idea.
  2. Click on "Add Project" to add a new project in Google Firebase. Then, you will find the below form.

  1. Give a project name and accept the terms and condition and click "Create Project". It will take some time to create a new project and redirect you to the Project Overview page.
  2. Now, you need to add an Android app to this project. You can add a new Android project by clicking on the Android icon. You can also add an iOS project if you want to create the iOS application for the same.

  1. In Project Overview, add an Android app. For that, click on the Android icon. It will open a new form.

  1. You will find the Android package name in the AndroidManifest.xml file in Android => App => main folder of your project.
  2. App nickname is optional
  3. For SHA-1 generation, go here.
  4. Now, download the google-service.json file and put in the Android => App folder of your project.
  5. Next, you need to configure some dependencies.
Project-level build.gradle (/build.gradle): means build.gradle file in the Android folder directly.
	
	```
	buildscript {  
dependencies {  
    // Add this line  
    classpath 'com.google.gms:google-services:4.2.0'  
}  
	```
App-level build.gradle (//build.gradle): means build.gradle file in Android = > App folder

// Add to the bottom of the file
apply plugin: 'com.google.gms.google-services’

_Note_ Don’t need to add implementation 'com.google.firebase:firebase-core:16.0.9' in dependencies
  1. In the next step, it will try to verify your app. For that, you need to run your app once or you can skip this step.
  2. Hurray! Your Android app has been created.
Enable Facebook Sign-In method in Firebase

Now, you need to enable the Facebook Sign-In method in Firebase. For that, you need to go to the Authentication tab and then Sign-in method tab. From there, enable Facebook Sign-In method. Please check the screenshot.

  1. You need an App ID and App secret. Get that from your Facebook app that you have created in previous steps. You will get the App Id and App secret from Settings => Basic. Note: Click on "Show App Secrete" to copy it.

  1. Copy the OAuth redirect from the Firebase app and paste in Facebook App. Facebook Login=>Settings >> Valid OAuth Redirect URIs. Please check the below screenshot.

    Copy from Firebase App

    ![This is image title](https://www.c-sharpcorner.com/article/facebook-login-in-flutter/Images/Facebook%20Login%20in%20Flutter12.png "This is image title")
    

    Paste in Facebook App (Login=>Settings Valid OAuth Redirect URIs)

    ![This is image title](https://www.c-sharpcorner.com/article/facebook-login-in-flutter/Images/Facebook%20Login%20in%20Flutter13.png "This is image title")
    
  2. You are all done with Firebase Setup. Congratulations!!!

Dependency Setup in Firebase project

Get back to the project and open the pubspec.yaml file in the root of the project. Pubspec.yaml is used to define all the dependencies and assets of the project.

  1. Add the below dependencies and save the file.

    firebase_auth:
    flutter_facebook_login:

  2. Please check the below screenshot so that you will get the idea of where to add the dependency.

  1. Run the "flutter packages get" command in terminal OR if you are using Visual Studio Code, then after saving the file, it will automatically run the command.
  2. Now, we are done with all dependency setup at project side as well…. :)

Now, we need to programmatically handle the Facebook login in Google Firebase. For that, we create a page, login_page.dart. I have attached a link of Git repo at the bottom of the article; you can take reference from there. Here, I will just define the main logic of Facebook login and logout.

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

  1. Login using Facebook.
Future < FirebaseUser > facebookLogin(BuildContext context) async {  
        FirebaseUser currentUser;  
        // fbLogin.loginBehavior = FacebookLoginBehavior.webViewOnly;  
        // if you remove above comment then facebook login will take username and pasword for login in Webview  
        try {  
            final FacebookLoginResult facebookLoginResult = await fbLogin.logInWithReadPermissions(['email', 'public_profile']);  
            if (facebookLoginResult.status == FacebookLoginStatus.loggedIn) {  
                FacebookAccessToken facebookAccessToken = facebookLoginResult.accessToken;  
                final AuthCredential credential = FacebookAuthProvider.getCredential(accessToken: facebookAccessToken.token);  
                final FirebaseUser user = await auth.signInWithCredential(credential);  
                assert(user.email != null);  
                assert(user.displayName != null);  
                assert(!user.isAnonymous);  
                assert(await user.getIdToken() != null);  
                currentUser = await auth.currentUser();  
                assert(user.uid == currentUser.uid);  
                return currentUser;  
            } catch (e) {  
                print(e);  
                return currentUser;  
            }  
        }  
  1. Logout from Facebook.
Future < bool > facebookLoginout() async {  
    await auth.signOut();  
    await fbLogin.logOut();  
    return true;  
}  
  1. When you sign up successfully, you can check that Google Firebase stores the user details on the server. Please check the screenshot.

You may encounter an error here, as mentioned below.

Error: import androidx.annotation.NonNull;

Solution

Put android.useAndroidX=true and android.enableJetifier=true in the android/gradle.properties file.

NOTE

  • I have commented one line in Facebook Sign-in method also. Try that by uncommenting it.

    fbLogin.loginBehavior = FacebookLoginBehavior.webViewOnly;

  • There will be an upgrade in any plugin like firebase_auth or flutter_facebook_login. So, if you find an error during login, please refer to its official documentation.

  • Please check out the Git repo for full source code. You need to add your google-services.json file in Android => App folder. Also, add android/app/src/main/res/values/strings.Xml with your Facebook App name, App Id and Secret.

  • Git Repo

Conclusion

Google Firebase is a very good service provider in terms of data validation, data storing, realtime data, and push notifications. We have only used the Google Firebase Facebook Sign-in feature in this post.

Thank you for reading!

Firebase Flutter Tutorial - Firebase CloudStorage in Flutter

Firebase Flutter Tutorial - Firebase CloudStorage in Flutter

In this Firebase Flutter Tutorial, we wrap the Firebase Storage package into a service to easily manage and upload the firebase storage.

Hello there Flutter Dev 🙋‍♂️ In this tutorial we will be going over Cloudstorge on Firebase and how to integrate that with your mobile application.

Today we'll do a simple task that is probably a very common task in mobile app development. We will provide the user with a UI to select and upload a photo, save that photo in a post and display that to them in a collection. The photo will be stored in Firebase CloudStorage and we'll use a URL to the photo to display to users in the app. We'll start off by updating the UI to allow us to upload a photo.

Cloud Storage Setup

Before we start with the code lets setup our cloud storage. Open up the firebase console and click on the storage icon in the left toolbar. Click on create bucket, choose the location and continue. You will now have what is called a "bucket" where you can store files. You can think of this as a hard drive that you access through a url web request. Each of the files here will have an access token / url that you can access only through that url with the attached token. You can set visibility by controling the access level for that download url token. This is the url we'll use to display the image in the app.

Implementation

Let go over a quick implementation overview. We'll create a service that wraps the provided firebase storage package. This service will take in a File object and a title and upload that to the storage. When the operation is complete we will return the url which is what we'll put inside of our post as the imageUrl. The file that we're passing in will be selected using the UI presented by the image picker library. Let's get to it.

Code setup

We start off by adding the firebase_storage and the image_picker package to the pubspec.

firebase_storage: ^3.1.1
image_picker: ^0.6.3+1

Firebase storage is to interact with the Firebase Cloud Storage, the image picker is to show the user a UI that will allow them to select an image from their device.

Cloud Storage Implementation

Under the services folder create a new file called cloud_storage_service.dart. We'll give it a function called uploadImage that Takes in a required file as well as a title. You can pass in the UID, or anything you'd like to identify your images by.

import 'package:firebase_storage/firebase_storage.dart';

class CloudStorageService {
  Future<CloudStorageResult> uploadImage({
    @required File imageToUpload,
    @required String title,
  }) async {

  }
}

class CloudStorageResult {
  final String imageUrl;
  final String imageFileName;

  CloudStorageResult({this.imageUrl, this.imageFileName});
}

To access the Firestore Storage instance we use the FirebaseStorage.instance static property. The storage library works similar to the firebase documents. You can get a reference to a file that doesn't exist yet and then add the data in there that you want. We'll get a reference to our future file using the title and the date epoch to keep it unique. Once we have our reference we will call putFile and pass it in the selected File. This will give us a StorageUploadTask. This object has an onComplete Future that returns a StorageTaskSnapshot (similar to firebase snapshot). We can await that future and once we have the snapshot we can use the StorageReference returned and get the downloadUrl. We'll return the url when the task is complete or null.

  Future<CloudStorageResult> uploadImage({
    @required File imageToUpload,
    @required String title,
  }) async {

    var imageFileName = title + DateTime.now().millisecondsSinceEpoch.toString();

    final StorageReference firebaseStorageRef = FirebaseStorage.instance
    .ref()
    .child(imageFileName);

    StorageUploadTask uploadTask = firebaseStorageRef.putFile(imageToUpload);

    StorageTaskSnapshot storageSnapshot = await uploadTask.onComplete;

    var downloadUrl = await storageSnapshot.ref.getDownloadURL();

    if (uploadTask.isComplete) {
      var url = downloadUrl.toString();
      return CloudStorageResult(
        imageUrl: url,
        imageFileName: imageFileName,
        );
    }

    return null;
  }

Open up the locator.dart file and register the CloudStorageService with the get_it instance.

locator.registerLazySingleton(() => CloudStorageService());

Image Selection Implementation

We'll start off by wrapping the ImagePicker library into our own class. This way our business logic is not dependent on any third party packages. It's something I like to do, if you go the additional step and add it behind an interface then you can mock it out during testing as well.

Create a new folder called utils. Inside create a new file called image_selector.dart

import 'package:image_picker/image_picker.dart';

class ImageSelector {
  Future<File> selectImage() async {
    return await ImagePicker.pickImage(source: ImageSource.gallery);
  }

}

I know it seems silly to have a class that wraps one line, but you can do much more with it than this. You can keep the file in memory until you're certain it's uploaded, you can have different sources passed in from different functions, etc. The main reason for this is to remove the dependency of ImagePicker from any of the code in the app that has to make use of the functionality.

Open up the locator.dart file and register the ImageSelector with the get_it instance.

locator.registerLazySingleton(() => ImageSelector());

Finally open up the CreatePostViewModel where we'll locate the selector and then make use of it in a function called selectAndUploadImage. We'll also import the CloudStorageService for later use. We'll use the selectImage function to set the image to upload and display that to the user in the UI.

class CreatePostViewModel extends BaseModel {
  final ImageSelector _imageSelector = locator<ImageSelector>();
  final CloudStorageService _cloudStorageService = locator<CloudStorageService>();

  File _selectedImage;
  File get selectedImage => _selectedImage;

  Future selectImage() async {
    var tempImage = await _imageSelector.selectImage();
    if(tempImage != null) {
      _selectedImage = tempImage;
      notifyListeners();
    }
  }
}

In the same viewmodel update the addPost function to upload the image if we're not editting the post. We'll then use that url as the imageUrl in the post. For error handling I would show a snack bar if the imageUrl comes back null that indicates to the user that the image upload has failed.

 Future addPost({@required String title}) async {
    setBusy(true);

    CloudStorageResult storageResult;

    if (!_editting) {
      storageResult = await _cloudStorageService.uploadImage(
          imageToUpload: _selectedImage, title: title);
    }

    var result;

     if (!_editting) {
      result = await _firestoreService.addPost(Post(
        title: title,
        userId: currentUser.id,
        imageUrl:  storageResult.imageUrl,
        imageFileName: storageResult.imageFileName
      ));
    } else {
      result = await _firestoreService.updatePost(Post(
        title: title,
        userId: _edittingPost.userId,
        documentId: _edittingPost.documentId,
        imageUrl: _edittingPost.imageUrl,
        imageFileName: _edittingPost.imageFileName,
      ));
    }

    ...
  }

Next, open the Post model and add the new imageFileName String that we'll use to later delete the post.

class Post {
  ...
  final String imageFileName;

  Post({
    ...
    this.imageFileName,
  });

  Map<String, dynamic> toMap() {
    return {
      ...
      'imageFileName': imageFileName,
    };
  }

  static Post fromMap(Map<String, dynamic> map, String documentId) {
    if (map == null) return null;

    return Post(
      ...
      imageFileName: map['imageFileName'],
      documentId: documentId,
    );
  }
}

Now we can go onto the UI for the functionality. First thing to do is update the CreatePostView and add a gesture detector onto the grey rectangle we're displaying. When tapped we'll call the selectImage function. We'll also add a conditional to make sure when an image is selected we show it in that grey block. Update the container in the create_post_view that has the text in it to the following.

GestureDetector(
  // When we tap we call selectImage
  onTap: () => model.selectImage(),
  child: Container(
    height: 250,
    decoration: BoxDecoration(
        color: Colors.grey[200],
        borderRadius: BorderRadius.circular(10)),
    alignment: Alignment.center,
    // If the selected image is null we show "Tap to add post image"
    child: model.selectedImage == null
        ? Text(
            'Tap to add post image',
            style: TextStyle(color: Colors.grey[400]),
          )
          // If we have a selected image we want to show it
        : Image.file(model.selectedImage),
  ),
)

If you run the app now, tap on the FAB, enter a title and tap on the image block you'll see the image picker pop up. Select an image and it should be showing in the grey block in place of the text :) Add the post by pressing the FAB and it'll send it up to the cloud and return you a url.

If you open up the cloud storage now you'll see a file with the title you enetered and a number after it. That's the image you uploaded. Next up is displaying the image.

Image display implementation

To display the images from the cloud storage we will use the cached_network_image package. Add it to your pubspec.

cached_network_image: ^2.0.0

Open up the post_item and we'll update the UI. First thing is to make sure when we have an image we don't want to give the list a fixed size. We'll check if there's an image. If there's an image we set the heigh to null (meaning wrap content), otherwise we set it to 60.

class PostItem extends StatelessWidget {
  final Post post;
  ...
  @override
  Widget build(BuildContext context) {
    return Container(
      // Check if we have an image and set it to null or 60
      height: post.imageUrl != null ? null : 60,
      margin: const EdgeInsets.only(top: 20),
      alignment: Alignment.center,
      child: Row(
        children: <Widget>[
          Expanded(
              child: Padding(
            padding: const EdgeInsets.only(left: 15.0),
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: <Widget>[
                // If the image is not null load the imageURL
                post.imageUrl != null
                    ? SizedBox(
                        height: 250,
                        child: CachedNetworkImage(
                          imageUrl: post.imageUrl,
                          placeholder: (context, url) =>
                              CircularProgressIndicator(),
                          errorWidget: (context, url, error) =>
                              Icon(Icons.error),
                        ),
                      )
                // If the image is null show nothing
                    : Container(),
                Text(post.title),
              ],
            ),
          )),
         ...
        ],
      ),
      ...
    );
  }
}

That's it. You can now, post an image to the cloud storage. Then see it load as an item in the list of posts :)

Delete on Cloud Storage

Last thing to do is to delete the image again when a post is removed. This can be done by simple getting the ref and calling delete on it. Open up the CloudStorageService and we'll add a delete function.

class CloudStorageService {
   Future deleteImage(String imageFileName) async {
    final StorageReference firebaseStorageRef =
        FirebaseStorage.instance.ref().child(imageFileName);

    try {
      await firebaseStorageRef.delete();
      return true;
    } catch (e) {
      return e.toString();
    }
  }
}

Open up the HomeViewModel, locate the CloudStorageService and then after deleting the post from the firestore db call the delete function on the CloudStorageService as well.

class HomeViewModel extends BaseModel {
  final CloudStorageService _cloudStorageService = locator<CloudStorageService>();

  Future deletePost(int index) async {
    var dialogResponse = await _dialogService.showConfirmationDialog(
      title: 'Are you sure?',
      description: 'Do you really want to delete the post?',
      confirmationTitle: 'Yes',
      cancelTitle: 'No',
    );

    if (dialogResponse.confirmed) {
      var postToDelete = _posts[index];
      setBusy(true);
      await _firestoreService.deletePost(postToDelete.documentId);
      // Delete the image after the post is deleted
      await _cloudStorageService.deleteImage(postToDelete.imageFileName);
      setBusy(false);
    }
  }

}

And That's it. Basic Cloud storage functionality wrapped into a service for easy use. Make sure to follow me on Youtube for the rest of the series. Until next week :)

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.

A Vue Facebook Login Component

A Vue Facebook Login Component

A Vue Facebook Login Component

A renderless component for composing Vue Facebook login

Install
npm install --save vue-facebook-login-component
Usage

To use the component in your template, simply import and register with your component.

Script

import VFacebookLogin from 'vue-facebook-login-component'

// OR, use cherry-pick export (better consistency)
import { VFBLogin as VFacebookLogin } from 'vue-facebook-login-component'

export default {
  components: {
    VFacebookLogin
  }
}

Template

<v-facebook-login app-id="966242223397117"></v-facebook-login>
Props
Name Type Default Note
value Object { connected: false } Used for one-way V-Model. [ *** ]
app-id String None Required. [ *** ]
version String 'v3.1' [ **, *** ]
options Object {} [ *, **, *** ]
login-options Object { scope: 'email' } [ *, **, *** ]
button-style Object {} [ * ]
loader-style Object {} [ * ]
token-style Object {} [ * ]
text-style Object {} [ * ]
transition Array [] Array of CSS transition values. Example:[ 'background-color 0.15s ease-in-out', 'padding 0.15s ease-in-out', ... ].

[ * ] - Properties should be camel-case.
[ ** ] - See Facebook for available values.
[ *** ] - Scope-component property.

Slots
Name Default Description
login 'Sign in with Facebook'
logout 'Sign out with Facebook'
working 'Please wait...'
before NONE Before all nested elements.
after NONE After all nested elements.
error 'Network Error' Shown on SDK load failure.
Events
Name Payload Description Note
sdk-init (sdk[Object]) Returns an object with
a Facebook SDK instance. [ * ]
login (response[Object]) User attempted login. [ * ]
logout (response[Object]) User attempted logout. [ * ]
connect Boolean User is connected. [ * ]
click None [ * ]

[ * ] - Scope-component event.

Scope component (Advanced Customization)

If props, slots and events do not satisfy your customization needs, you can use an underlying component called v-fb-login-scope. This component uses the render prop (known as "scoped-slot" in Vue) approach for composition. This means, it does not render any html or css, hence it has no added-value on its own. It only exposes a scoped-slot with attributes and methods that are committed as API.

Props/Events

Refer to the tables above for scope-component specific props/events.

Scoped-Slot Scope (Attributes and Methods)

Name Type Description
login Function Login handler.
logout Function Logout handler.
toggleLogin Function Toggles login/logout.
working Boolean SDK-initialization/login/logout is currently taking place.
connected Boolean User is logged in.
disconnected Boolean User is logged out.
enabled Boolean Button is enabled.
disabled Boolean Button is disabled.

Scope component example (for a full example see source).

<template>
  <v-facebook-login-scope>
    <!-- Compose HTML/CSS here, otherwise nothing will be rendered -->
    <button slot-scope="scope">
      <!-- Compose with `scope` here -->
    </button>
  </v-facebook-login-scope>
</template>

<script>
  import { VFBLoginScope } from 'vue-facebook-login-component'

  export default {
    components: {
      VFBLoginScope
    }
  }
</script>
Loading Facebook SDK

This component embeds the Facebook SDK snippet, so you don't have to do it yourself. However, if you want to embed it yourself, you can do so and the component will pick up the SDK instance instead.

IE support

Add babel-polyfill to your app dependencies.

Development

Fork, clone and use the following scripts.

For component:

npm start

For documentation (includes a demo):

npm run docs

DEMO || DOWNLOAD