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!
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 6 is necessary for this.
Also, if you're doing this on iOS, don't forget to do the steps on iOS integration here, changing the Google Sign-in Section within ios/Runner/Info.plist with the correct CFBundleURLSchemes.
With this out of the way, we're onto some programming.
main.dart which is just setting the title, a dark theme and home as the
logger.dart file is just a util function that prints with a tag like this:
[AUTH] Something happened! on your log.
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.
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
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 code text input for making additional requests, like registering your user in your own API, saving the data locally, etc.
_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.
_errorMessagewill be displayed on each
TextFieldin case of a validation error;
_verificationIdwill be used by Firebase to verify our code received from a SMS message;
_codeTimeris a timer so we can send the code again after
_timeOut's duration (and cancel it in case of success);
_isRefreshingcontrols whether the "spinning wheel" of
_codeTimedOutis a control variable for the timer set before;
_codeVerifiedis 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.);
_authis the instance for FirebaseAuth;
_phoneNumberis just a local cache for the phoneNumber input for when we try to resend the SMS;
_googleSignInis the instance for GoogleSignIn;
_googleUseris the instance of
GoogleSignInAccountthat will be filled later.
Now we're onto the methods defined (literally, with
typedef) based on the default implementation for both Android and iOS.
FirebaseUseras an argument and will be only executed in Android. It happens when the code is automatically retrieved from the SMS, without any user input.
PhoneVerificationFailedtakes place when the verification fails, and we should show a
SnackBarto the user (or any method of error handling you might like) telling them to try again.
PhoneCodeSentmeans 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).
PhoneCodeAutoRetrievalTimeoutmeans 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
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.
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.
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.
_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.
_onCodeVerified and if everything goes right, a basic screen with profile picture, name, e-mail and phone number will be displayed.
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
_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!