Adding sign in with Google and phone authentication to your Flutter app
Okay, this is my first publication here and being very short on introductions (updated on 2019/05/15): I've been working with Flutter for a year as of now, and I've published several apps with it! Between a chat app with only one message, a manager for your car recalls, a style-based social network and a pet breed detector , being the last two AI-driven, you can already tell that Flutter has been amazing for me and I hope I'm able to help you with this tutorial! You can also see more info (and some blog posts) on my page. Want my help with a project? Just hit me up with an e-mail!
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 6 and 7 are 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 data.
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 code text input for making additional requests, like registering your user in your own API, saving the data locally, etc.
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 eachTextField
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" ofRefreshIndicator
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;_phoneNumber
is just a local cache for the phoneNumber input for when we try to resend the SMS;_googleSignIn
is the instance for GoogleSignIn;_googleUser
is the instance ofGoogleSignInAccount
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 anAuthCredential
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. Below on the code, we're just passing_linkWithPhoneNumber
to this parameter onverifyPhoneNumber
.PhoneVerificationFailed
takes place when the verification fails, and we should show aSnackBar
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 toSMS_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 asynchronous 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 FirebaseUser
's method: linkWithPhoneNumber
, 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.
_finishSignIn
calls _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 _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!