Flutter: Adding sign in with Google and phone authentication to your app

Okay, this is my first publication here and being very short on introductions: I'm a recently graduated software engineer and I've been in love with Flutter since it launched the first public alpha. I have 3 years of experience with Android development and 2 years with Django and its amazing API framework django-rest-framework mostly with this project, which was my final one on the graduation. You can also see more info (and some blog posts) here.

Disclaimer: First of all, I’m assuming is not your first contact with Flutter, IDEs and git. If it is, start here. Also, this is a "walkthrough" for my implementation of this part of firebase_auth and won't work if you don't follow the configuration steps properly, so make sure you read everything carefully!


Along with adding phone authentication, this tutorial will add sign in with Google to make the experience seamless with Firebase and such.

I’ll be explaining what my code does instead of making you write what I already wrote, so it’ll be easier for both of us. Be mindful and instead of copying and pasting everything from my code, try to read here about what it does and how. Also, type it! It’ll be better for your memory.

You can start by cloning my repo, which won't work if you just run it because there's a few configuration steps you need to follow below! You can also do this for your own project and just use my implementation as a starting point!

git clone https://github.com/gildaswise/flutter_phone_auth.git

Don't forget to run flutter packages get or the equivalent for your IDE!

Then, we're heading to Firebase's console to create a new project! You should see a screen like this one on the side.

After creating it and waiting a few seconds, we should now be on the dashboard.

We're heading to Authentication to make a few changes on Sign-in method. Just click on Phone, then enable and save. Same thing for Google, then enable and save. Your Sign-in method tab should look like this:

We are not done yet! Fortunately, Google already made a Codelab that ensures you add platform-specific configuration to your Firebase app. Just click here and come back once you're done with this step. Remember, only Step 5 is necessary for this.

Also, if you're doing this on iOS, don't forget to do the steps on iOS integration here.


With this out of the way, we're onto some programming.

Starting with main.dart which is just setting the title, a dark theme and home as the AuthScreen at routes/auth.dart.

The logger.dart file is just a util function that prints with a tag like this: [AUTH] Something happened! on your log.

The widgets folder contains a custom version of enzotiezzi's masked_text where it also has a getter for the unmasked string (I'll make a proper PR soon), an also custom version of kentcb's RefreshIndicator (this one behaves like SwipeRefreshLayout on Android instead of being always asyncronous) and a GoogleSignInButton that I made based on Google's guidelines for it.

The routes folder has all we need to really start. main_screen.dart will be showing the data we gathered from Google's sign in and the properly formatted phone number from FirebaseUser.


On auth.dart is where most of this tutorial will be spent. It is a screen based on AuthStatus for an easier learning experience. I still want to add animations to it and make it cleaner overall but there's always room for improvement. This will be a wall of text as I explain literally everything and I'm kindly asking you to read it all. It's a complex flow and I condensed it all in one file (that's not the best option, I know, but for a short Medium tutorial I thought it would be the best way of explaining this).

Each item on AuthStatus represents quite literally the status of the UI. On SOCIAL_AUTH only the GoogleSignInButton will be visible, then PHONE_AUTH means only the phone input will be visible and the same goes to SMS_AUTH for the text input which will receive the verification code. PROFILE_AUTH is a special case where it just blocks interaction with the verification codetext input for making additional requests, like registering your user in your own API, saving the data locally, etc.

class _AuthScreenState extends State<AuthScreen> {
  static const String TAG = "AUTH";
  AuthStatus status = AuthStatus.SOCIAL_AUTH;
  // Keys
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
final GlobalKey<MaskedTextFieldState> _maskedPhoneKey = GlobalKey<MaskedTextFieldState>();
  // Controllers
TextEditingController smsCodeController = TextEditingController();
TextEditingController phoneNumberController = TextEditingController();
  // Variables
String _errorMessage;
String _verificationId;
Timer _codeTimer;
  bool _isRefreshing = false;
bool _codeTimedOut = false;
bool _codeVerified = false;
Duration _timeOut = const Duration(minutes: 1);
  // Firebase
final FirebaseAuth _auth = FirebaseAuth.instance;
final GoogleSignIn _googleSignIn = GoogleSignIn();

GoogleSignInAccount _googleUser;

On _AuthScreenState we start by defining the tag that will be used for our logger, then the default status as SOCIAL_AUTH, which means we need to do Google's sign in and the GoogleSignInButton will be visible and interactive.

Then, a GlobalKey for our Scaffold so we can display a SnackBar whenever an error occurs and a GlobalKey for MaskedTextField to get the unmaskedText property later. Shortly after, a TextEditingController is defined for each text field (phone and verification code) and then our variables.

  • _errorMessage will be displayed on each TextField in case of a validation error;
  • _verificationId will be used by Firebase to verify our code received from a SMS message;
  • _codeTimer is a timer so we can send the code again after _timeOut's duration (and cancel it in case of success);
  • _isRefreshing controls whether the "spinning wheel" of RefreshIndicator is visible;
  • _codeTimedOut is a control variable for the timer set before;
  • _codeVerified is also a control variable so we can see if the SMS code is already verified and we can try anything after this is already done (finishing sign-up for your own API, saving it locally to a database, etc.);
  • _auth is the instance for FirebaseAuth;
  • _googleSignIn is the instance for GoogleSignIn;
  • _googleUser is the instance of GoogleSignInAccount that will be filled later.

Now we're onto the methods defined (literally, with typedef) based on the default implementation for both Android and iOS.

  • PhoneVerificationComplete takes a FirebaseUser as an argument and will be only executed in Android. It happens when the code is automatically retrieved from the SMS, without any user input.
  • PhoneVerificationFailed takes place when the verification fails, and we should show a SnackBar to the user (or any method of error handling you might like) telling them to try again.
  • PhoneCodeSent means the code was successfully sent to the number inputted by the user, here we use this function to change the state of the UI to SMS_AUTH, where the user will input the code when received (or request it again after 1 minute).
  • PhoneCodeAutoRetrievalTimeout means that the time for the auto retrieval on Android has expired and we should expect the user to input the code manually, here it is used to trigger _codeTimedOut.

After these methods are defined, two TextStyle are defined so I can use them more than once when later building the widgets. I also override the dispose() method so we can just cancel the timer as it will call setState and that causes errors.


We're now heading into the async part of this tutorial (starting at line 112 of auth.dart). All the methods here are asyncronous so we can wait for their result before going on with anything.

The first one, _updateRefreshing, controls our ReactiveRefreshIndicator's state, making sure it is not displaying any animations before we start it. Then, _showErrorSnackbar makes sure we aren’t showing our refresh indicator before showing an SnackBar with an error.

SOCIAL_AUTH

Then we have the first meaningful method for this tutorial! _signIn will call GoogleSignIn's own signIn method and then the user will select their Google account (on Android) or log in it (on iOS). Once that's done, we can move the state up to PHONE_AUTH, where we'll receive the user's phone number.

PHONE_AUTH

After this, we have _submitPhoneNumber which validates the input, sets the error message if any, and then calls _verifyPhoneNumber right below it. This method has the same name of FirebaseAuth's method because, well, that's all it does. We pass the methods defined above and the validated phone number (which I’m hardcoding with Brazil's country code, as the app where I first did this is Brazil-only for now, but you should replace it with your country’s or make a selector/text input for it).

_submitSmsCode does the same as its phone number counterpart: validates the input, sets the error message if any and then calls a wrapper for FirebaseAuth 's method: signInWithPhoneNumber, which takes the _verificationId defined before and the code from smsCodeController. If it succeeds (by returning a FirebaseUser), then we just finished the auth flow!

We logged in with our Google account, then sent a verification code to a phone number, submitted it to Firebase and got it back verified.

SMS_AUTH

_onCodeVerified right below it just takes the FirebaseUser, verifies if it's null and sets the status to PROFILE_AUTH, which is an additional state for you to register your user to your private API, locally or anywhere else, where the text input and the button for the verification code are disabled.

_finishSignIn calls _onCodeVerified and if everything goes right, a basic screen with profile picture, name, e-mail and phone number will be displayed.

main_screen.dart

After this, there's a bunch of build functions for widgets. They might not be suiting your design and they're just there so we have something interactive. All messages are hardcoded but you should integrate internationalization in your real app.

The way I'm doing state management in this UI is probably not the best for a case like this and I'm open to suggestions down on the comments and PRs on the repo!

Down there also is _buildBody and _onRefresh which are methods based on the state of the application. Again, this might not be the best way but it's enough for teaching the simplest way.

And that's it, folks. Everything on this repo is now explained and I hope it can serve as a help for you who are trying to integrate phone authentication within your Flutter app!

Thank you for your time and I hope this helped you setting up your Google and phone authentication! Sorry if anything is hard to understand and I'll stand by the comments for any help! Also, thanks to Beatriz which helped a lot with grammar related fixes here!