Firebase Authentication in Flutter

Firebase Authentication in Flutter

Firebase Authentication in Flutter - Production Patterns. This tutorial will cover the implementation and architecture for Firebase Authentication. We use Firebase Authentication in production to keep my code maintainable and easy to manage. We cover the basic login and sign up functionality.

Today we'll be going over the production practices I follow when implementing email authentication using Firebase in Flutter. We'll be building a social media app called compound. It's called compound because that's the middle word of the book in front of me on my desk. "The Compound Effect". Even if you don't want to build a social media app, I'll be teaching you the principles you need to apply to a firebase project to build literally any app you want.

The Architecture

If you don't know, I use an Mvvm Style architecture with Provider for my UI / Business logic separation and get_it as a service locator. I've found this to be the most consistent and easy to understand architecture that I've used in production. It keeps implementations short and specific. In short the architecture specifies that each view or basic widget can have it's own ViewModel that contains the logic specific to that piece of UI. The ViewModel will make use of services to achieve what the user is requesting through their interactions.

Services is where all the actual work happens. ViewModels make use of the services but doesn't contain any hard functionality outside of conditionals and calling services. So, to get to the task at hand. We'll have an Authentication service that we'll use to sign in or sign up with that will store an instance of the current firebase user for us to use when required. We will have two views, Login and SignUp view which will make of the two functions on the service. The entire backend of the application will be built using Firebase so make sure to go to your console and login with a gmail account.

Setup Firebase Project

Open up the firebase console and click on "Add Project". Call it "compound", go next, select your account and then create. This will take a few seconds to setup. When it's complete click on continue and you'll land on the overview page.

Click on the Android Icon (or iOS) and add your package name, I'll set mine to com.filledstacks.compound. I'll set the nickname to "Compound". Register the app and then download the google-services.json file. If you have your own project or want to use my starting code, which you can download here, open up the code and place the google-service.json file in the android/app folder. Then open the build.gradle file in the android/app folder and change the applicationId to match the one you entered for your Firebase project.

Setup in code

Open up the pubspec.yaml and add the firebase_auth plugin.

firebase_auth: ^0.15.3

Then we have to enable the google services. Open the build.gradle file in the android folder and add the google services dependency.

    dependencies {
        // existing dependencies
        classpath 'com.android.tools.build:gradle:3.5.0'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"

        // Add the google services classpath
        classpath 'com.google.gms:google-services:4.3.0'
    }

Open up the android/app/build.gradle file and apply the google services plugin. Add the following line at the bottom of the file.

// ADD THIS AT THE BOTTOM
apply plugin: 'com.google.gms.google-services'

That's it for the Android setup. Lets continue with the Firebase project. Once you've created the app you can go next and skip the firebase comms check that they do. On the left hand side, click on the Authentication Icon. The third icon from top (might change). Click on the Setup sign in methods button and click on email / password and enable it. That's it for the project setup, we'll get back to the Firebase console in the next episode.

Authentication Implementation

The starting code that I provided has a few things setup already.

  1. It contains the provider_architecture package which we use for the MvvmStyle bindings.
  2. It has an InputField widget which is styled how I want it.
  3. It has the locator for get_it setup like this
  4. It has a Navigation Service so we can navigate from the ViewModels and other services
  1. It has a Dialog Service for showing default dialogs
  1. It has the login view as well as the sign up view created and styled.

This is to make sure we keep the app to the point and only show the firebase parts. We'll be creating the Authentication service and then using it in the viewmodels, which are completely empty.

Authentication Service

The responsibility of the AuthenticationService in this case is to wrap the Firebase Authentication functionality for us. It will send the info we entered, and then tell us if it's successful or not. If it fails we return an error message to show the user. Under the services folder create a new file called authentication_service.dart.

import 'package:flutter/foundation.dart';

class AuthenticationService {
  Future loginWithEmail({@required String email, @required String password}) {
    // TODO: implement loginWithEmail
    return null;
  }

  Future signUpWithEmail({@required String email, @required String password}) {
    // TODO: implement signUpWithEmail
    return null;
  }
}

We'll start off keeping a reference to the FirebaseAuth instance locally. Then we'll perform signInWithEmailAndPassword and store the result in a variable called user. If there's no errors we'll check if the user is not null and return that value. If it fails we return the message from the error.

final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;

Future loginWithEmail({
    @required String email,
    @required String password,
}) async {
    try {
        var user = await _firebaseAuth.signInWithEmailAndPassword(
            email: email, password: password);
        return user != null;
    } catch (e) {
        return e.message;
    }
}

Sign up looks very similar. The only difference is that the result of the createUserWithEmailAndPassword function returns a FirebaseAuth object instead of the user like login.

Future signUpWithEmail({
    @required String email,
    @required String password,
}) async {
    try {
        var authResult = await _firebaseAuth.createUserWithEmailAndPassword(
            email: email, password: password);
        return authResult.user != null;
    } catch (e) {
        return e.message;
    }
}

That's it for the AuthenticationService. Open up the locator.dart file and register the service as a lazy singleton. All that means is that there will only ever be 1 authentication service in existence, and we'll lazily create it once it has been requested the first time.4

void setupLocator() {
  locator.registerLazySingleton(() => NavigationService());
  locator.registerLazySingleton(() => DialogService());
  locator.registerLazySingleton(() => AuthenticationService());
}
Signup Logic

We'll start with sign up so that we can then perform a login afterwards. Open up the main.dart file and make sure home is set to SignUpView. Then open up the signup_view_model.dart file. We'll start by retrieving the AuthenticationService, NavigationService and DialogService from the locator. Then we'll create a function called SignUp that takes the email and password. In this function we'll set the view to busy before requesting, do the sign up. Then check the result, if it's a bool and it's true then we navigate to the HomeView. If it's false we'll show a general dialog, if it's a string we'll show the content as a dialog.

class SignUpViewModel extends BaseModel {
  final AuthenticationService _authenticationService =
      locator<AuthenticationService>();
  final DialogService _dialogService = locator<DialogService>();
  final NavigationService _navigationService = locator<NavigationService>();

  Future signUp({@required String email, @required String password}) async {
    setBusy(true);

    var result = await _authenticationService.signUpWithEmail(
        email: email, password: password);

    setBusy(false);
    if (result is bool) {
      if (result) {
        _navigationService.navigateTo(HomeViewRoute);
      } else {
        await _dialogService.showDialog(
          title: 'Sign Up Failure',
          description: 'General sign up failure. Please try again later',
        );

      }
    } else {
      await _dialogService.showDialog(
        title: 'Sign Up Failure',
        description: result,
      );
    }
  }
}

Open up the SignUpView file. Update the BusyButton to take in the busy property from the model and in the onPressed function call model.signUp.

 BusyButton(
    title: 'Sign Up',
    busy: model.busy,
    onPressed: () {
        model.signUp(
        email: emailController.text,
        password: passwordController.text,
        );
    },
)

If you run the app now, enter some details and login you'll see it navigate to the HomeView. If you want to see the error dialog enter a password with less than 6 characters and you'll see the dialog pop up. Also if you've already signed up you can try signing up with the same email again and you'll get a friendly error message :)

Login Logic

The login logic logic is literally exactly the same as the sign up logic. Being able to refactor for shared code is a good skill to have, I'll leave it up to you as an exercise to do. For now we'll write non dry code by simple repeating the pattern. Open up the login_view_model.dart

class LoginViewModel extends BaseModel {
  final AuthenticationService _authenticationService =
      locator<AuthenticationService>();
  final DialogService _dialogService = locator<DialogService>();
  final NavigationService _navigationService = locator<NavigationService>();

  Future login({@required String email, @required String password}) async {
    setBusy(true);

    var result = await _authenticationService.loginWithEmail(
        email: email, password: password);

    setBusy(false);

    if (result is bool) {
      if (result) {
        _navigationService.navigateTo(HomeViewRoute);
      } else {
        await _dialogService.showDialog(
          title: 'Login Failure',
          description: 'Couldn\'t login at this moment. Please try again later',
        );
      }
    } else {
      await _dialogService.showDialog(
        title: 'Login Failure',
        description: result,
      );
    }
  }
}

Open the login view. Pass the busy value to the BusyButton and in the onPressed function call the login function.

 BusyButton(
    title: 'Login',
    busy: model.busy,
    onPressed: () {
        model.login(
            email: emailController.text,
            password: passwordController.text,
        );
    },
)

Open up the main.dart file and change home to LoginView. If you re-run the code now you'll land on the LoginView. Enter the details you entered, click login and you're done :) . This is just the start of the app, we'll add functionalities a normal app would have throughout the rest of the series. In the next tutorial we'll make sure once we're signed in we go straight to the HomeView. We'll also create a user profile, make sure it's always available when the app is open and add roles (for later use ;) ).

I decided to ask you guys to start sharing the tutorials more, I'm still seeing some unmaintainable code when new clients come to me. We have to spread the architecture and code quality love around and make that the core focus when building apps. Until next time, Dane Mackier.

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 :)

Apple Sign In with Flutter & Firebase Authentication

Apple Sign In with Flutter & Firebase Authentication

In this tutorial we'll see how to add Apple Sign In to our Flutter apps from scratch. Learn how to implement Apple Sign In with Flutter & Firebase Authentication (from scratch), and give your iOS users a convenient way of signing into your app. Apple Sign In with Flutter & Firebase Authentication.

In this tutorial we'll see how to add Apple Sign In to our Flutter apps from scratch.

Apple Sign In is a new authentication method that is available on iOS 13 and above.

It is very convenient, as your iOS users already have an Apple ID, and can use it to sign in with your app.

So just as you would offer Google Sign In on Android, it makes sense to offer Apple Sign In on iOS.

We will use the Apple Sign In Flutter plugin available on pub.dev.

Note: this plugin supports iOS only, and you can only use this on devices running iOS 13 and above.

Prerequisites

  • Xcode 11 installed
  • An Apple Developer Account
  • An iOS 13.x device or simulator, signed in with an Apple ID
Project Setup

After creating a new Flutter project, add the following dependencies to your pubspec.yaml file:

dependencies:
  firebase_auth: ^0.15.3
  apple_sign_in: ^0.1.0
  provider: ^4.0.1

Note: we will use Provider for dependency injection, but you can use something else if you prefer.

Next, we need add Firebase to our Flutter app. Follow this guide for how to do this:

After we have followed the required steps, the GoogleService-Info.plist file should be added to our Xcode project.

And while in Xcode 11, select the Signing & Capabilities tab, and add "Sign In With Apple" as a new Capability:

Once this is done, ensure to select a team on the Code Signing section:

This will generate and configure an App ID in the "Certificates, Identifiers & Profiles" section of the Apple Developer portal. If you don't do this, sign-in won't work.

As a last step, we need to enable Apple Sign In in Firebase. This can be done under Authentication -> Sign-in method:

This completes the setup for Apple Sign In, and we can dive into the code.

Checking if Apple Sign In is available

Before we add the UI code, let's write a simple class to check if Apple Sign In is available:

import 'package:apple_sign_in/apple_sign_in.dart';

class AppleSignInAvailable {
  AppleSignInAvailable(this.isAvailable);
  final bool isAvailable;

  static Future<AppleSignInAvailable> check() async {
    return AppleSignInAvailable(await AppleSignIn.isAvailable());
  }
}

Then, in our main.dart file, let's modify the entry point:

void main() async {
  // Fix for: Unhandled Exception: ServicesBinding.defaultBinaryMessenger was accessed before the binding was initialized.
  WidgetsFlutterBinding.ensureInitialized();
  final appleSignInAvailable = await AppleSignInAvailable.check();
  runApp(Provider<AppleSignInAvailable>.value(
    value: appleSignInAvailable,
    child: MyApp(),
  ));
}

The first line prevents an exception that occurs if we attempt to send messages across the platform channels before the binding is initialized.

Then, we check if Apple Sign In is available by using the class we just created.

And we use Provider to make this available as a value to all widgets in our app.

Note: this check is done upfront so that appleSignInAvailable is available synchronously to the entire widget tree. This avoids using a FutureBuilder in widgets that need to perform this check.

Adding the UI code

Instead of the default counter app, we want to show a Sign In Page with a button:

import 'package:apple_sign_in/apple_sign_in.dart';
import 'package:apple_sign_in_firebase_flutter/apple_sign_in_available.dart';
import 'package:apple_sign_in_firebase_flutter/auth_service.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

class SignInPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final appleSignInAvailable =
        Provider.of<AppleSignInAvailable>(context, listen: false);
    return Scaffold(
      appBar: AppBar(
        title: Text('Sign In'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(6.0),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [            
            if (appleSignInAvailable.isAvailable)
              AppleSignInButton(
                style: ButtonStyle.black, // style as needed
                type: ButtonType.signIn, // style as needed
                onPressed: () {},
              ),
          ],
        ),
      ),
    );
  }
}

Note: we use a collection-if to only show the AppleSignInButton if Apple Sign In is available. See this video for UI-as-code operators in Dart.

Back to our main.dart file, we can update our root widget to use the SignInPage:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Apple Sign In with Firebase',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.indigo,
      ),
      home: SignInPage(),
    );
  }
}

At this stage, we can run the app on an iOS 13 simulator and get the following:

Adding the authentication code

Here is the full authentication service that we will use to sign in with Apple (explained below):

import 'package:apple_sign_in/apple_sign_in.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/services.dart';

class AuthService {
  final _firebaseAuth = FirebaseAuth.instance;

  Future<FirebaseUser> signInWithApple({List<Scope> scopes = const []}) async {
    // 1\. perform the sign-in request
    final result = await AppleSignIn.performRequests(
        [AppleIdRequest(requestedScopes: scopes)]);
    // 2\. check the result
    switch (result.status) {
      case AuthorizationStatus.authorized:
        final appleIdCredential = result.credential;
        final oAuthProvider = OAuthProvider(providerId: 'apple.com');
        final credential = oAuthProvider.getCredential(
          idToken: String.fromCharCodes(appleIdCredential.identityToken),
          accessToken:
              String.fromCharCodes(appleIdCredential.authorizationCode),
        );
        final authResult = await _firebaseAuth.signInWithCredential(credential);
        final firebaseUser = authResult.user;
        if (scopes.contains(Scope.fullName)) {
          final updateUser = UserUpdateInfo();
          updateUser.displayName =
              '${appleIdCredential.fullName.givenName} ${appleIdCredential.fullName.familyName}';
          await firebaseUser.updateProfile(updateUser);
        }
        return firebaseUser;
      case AuthorizationStatus.error:
        print(result.error.toString());
        throw PlatformException(
          code: 'ERROR_AUTHORIZATION_DENIED',
          message: result.error.toString(),
        );

      case AuthorizationStatus.cancelled:
        throw PlatformException(
          code: 'ERROR_ABORTED_BY_USER',
          message: 'Sign in aborted by user',
        );
    }
    return null;
  }
}

First, we pass a List<Scope> argument to our method. Scopes are the kinds of contact information that can be requested from the user (email and fullName).

Then, we make a call to AppleSignIn.performRequests and await for the result.

Finally, we parse the result with a switch statement. The three possible cases are authorized, error and cancelled.

Authorized

If the request was authorized, we create an OAuthProvider credential with the identityToken and authorizationCode we received.

We then pass this to _firebaseAuth.signInWithCredential(), and get an AuthResult that we can use to extract the FirebaseUser.

And if we requested the full name, we can update the profile information of the FirebaseUser object with the fullName from the Apple ID credential.

Error or Cancelled

If authentication failed or was cancelled by the user, we throw a PlatformException that can be handled by at the calling site.

Using the authentication code

Now that our auth service is ready, we can add it to our app via Provider like so:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Provider<AuthService>(
      create: (_) => AuthService(),
      child: MaterialApp(
        title: 'Apple Sign In with Firebase',
        debugShowCheckedModeBanner: false,
        theme: ThemeData(
          primarySwatch: Colors.indigo,
        ),
        home: SignInPage(),
      ),
    );
  }
}

Then, in our SignInPage, we can add a method to sign-in and handle any errors:

Future<void> _signInWithApple(BuildContext context) async {
  try {
    final authService = Provider.of<AuthService>(context, listen: false);
    final user = await authService.signInWithApple(
        requestEmail: true, requestFullName: true);
    print('uid: ${user.uid}');
  } catch (e) {
    // TODO: Show alert here
    print(e);
  }
}

Finally, we remember to call this on the callback of the AppleSignInButton:

AppleSignInButton(
  style: ButtonStyle.black,
  type: ButtonType.signIn,
  onPressed: () => _signInWithApple(context),
)
Testing things

Our implementation is complete, and we can run the app.

If we press the sign-in button and an Apple ID is not configured on our simulator or device, we will get the following:

After signing in with our Apple ID, we can try again, and we will get this:

After continuing, we are prompted to enter the password for our Apple ID (or use touch ID/face ID if enabled on the device). If we have requested full name and email access, the user will have a chance edit the name, and choose to share or hide the email:

After confirming this, the sign-in is complete and the app is authenticated with Firebase.

Note: if the sign-in screen is not dismissed after authenticating, it's likely because you forgot to set the team in the code signing options in Xcode.

The next logical step is to move away from the SignInPage and show the home page instead. This can be done by adding a widget above the SignInPage, to decide which page to show depending on the onAuthStateChaged stream of FirebaseAuth.

Congratulations, you have now enabled Apple Sign In in your Flutter app! Your iOS users are grateful. 🙏

Full Source Code is here on GitHub.

Happy coding!

How to Create Liquid Swipe Animation in Flutter | Liquid Swipe Flutter

How to Create Liquid Swipe Animation in Flutter | Liquid Swipe Flutter

Today tutorial, I am going to show you how to create Liquid Swipe Animation in Flutter. liquid Swipe animation is amazing and its Created for iOS Platform and f

Today tutorial, I am going to show you how to create Liquid Swipe Animation in Flutter. liquid Swipe animation is amazing and its Created for iOS Platform and for React Native Platform. this animation Or Liquid Swipe Animation is Inspired by Cuberto.

Cuberto Liquid Swipe Animation

For this animation, we are will Need liquid_swipe Package. Liquid Swipe is the Unveils a New Page like Liquid Animation Just like the Above animation.

📱 What we're going to Build

How to Create Liquid Swipe Animation in Flutter

Let's Start the Tutorial

If you Don't know how to Create a Flutter app, check out Getting started with Flutter official tutorial.

Read More