Building HashTrack with Flutter: Authentication logic

If you haven’t check out the previous post in these series, then you can do so here:

You can also find the code for this guide here:

Now that we have the foundations of our application built, we’re going to go ahead and setup the first screens which will be shown to the user when opening the app — the authentication screens. This is going to require us to create and configure several different components:

  • A sign-in screen — Providing a button that allows the user to sign-in with their Google account using Firebase Authentication
  • A splash screen — Used to show a loading indicator whilst the authenticated state is being checked

With these screens we’re going to setup an initial app launch flow of:

  • Check if the user is signed in (show a progress indicator whilst checking this)
  • If the user isn’t signed in then display the login screen
  • Allow the user to sign-in with their Google account

For this we’re going to need to have access to the google sign-in class from the firebase authentication package. We’ll need access to this from our app.dart class as this is where this authentication is going to happen — because of this we’ll open this class up and add this GoogleSignIn class as a required parameter to the class constructor:

class HashtrackApp extends StatelessWidget {
final GoogleSignIn googleSignIn;

        @required this.googleSignIn});

Now that we’ve added this to the constructor, we need to pass in the instance where the class is instantiated. We’re going to open up our main.dart class and passing a GoogleSignIn instance. It’s good practice to pass down our required dependencies into child classes — in this case if we require any configuration to be applied to these classes, this can happen before they are passed into our HashtrackApp instance.

runApp(new HashtrackApp(
  googleSignIn: new GoogleSignIn(),

At this point we now have access to the class that will be used to authenticate our users, we’ll leave this as it is until we get to that stage in this post.

Now, before we get on to handling the actual authentication of our users we need to setup some initial UI states. To begin with, when the app is first opened we need to check if the user is currently authenticated or not. In this situation we’ll show progress indicator widget on the screen whilst we check this state, and then display some other form of state once we know whether or not the user is authenticated.

For this we’re going to start by creating a new widget called SplashScreen, this is going to be a StatelessWidget because our widget does not need to hold any state — it is simply going to display a Progress Indicator as a child:

Now that we’ve created this widget, let’s check how it looks on our device. For now we’ll just set it as the home widget of our application and then run the app on our emulator:

home: new SplashScreen(),

Now that we have this loading widget ready to display for to our user, we need to implement the logic that will check the authenticated state so that we know when to display the widget. For this we’re going to use the same approach that I outlined here — this allows us to listen for authentication state events:

Widget _getLandingPage() {
return new StreamBuilder<FirebaseUser>(
      stream: FirebaseAuth.instance.onAuthStateChanged,
      builder: (BuildContext context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return new SplashScreen();
        } else {
          // return login screen

Here we’ve defined a new function called _getLandingPage(), this returns a StreamBuilder instance which will check the connection state of the firebase state. This will now provide us with a way to listen to authentication state events, and returning the corresponding widget to display to the user to reflect this.

We now want to replace the home widget of our application with this function that we have just declared:

home: _getLandingPage(),

The next thing we need to do is create the login screen that will be displayed to the user. This is going to:

  • Show the authentication UI state
  • Handle the authentication of the user

For now we’ll create new login.dart class to begin housing the authentication widget contents:

Here we pass in two classes via the constructor, both the FirebaseAuth an GoogleSignIn are used to handle the authentication flow. Now that we have the base part of this class setup we need to create the widget contents that will be shown on screen. For examples sake, we’re going to keep this screen super simple, so we’re going to add three UI widgets:

  • Image — To display some form of hashtag logo
  • Text — To display some welcome text for the user
  • Button — To allow the user to connect using their Google account

Both the text and button widgets are going to require some text to display — therefore we need to add some strings to our AppLocalizations class:

Now that we’ve created these strings we can go ahead and setup the widget content to be display within the UI. Here is the code that will used to define this:

Let’s take a quick run through what is happening here:

  • Within our Scaffold widget we begin by defining a new Container. We give this a white background for now as we haven’t setup the theming for our application just yet.
  • Next we define a Column widget, this will allow us to display our items in a vertical manner — we also declare that this widget should take up the maximum space on the main axis.
  • Inside of the column we then define three widgets. The first is the Image widget, here I pulled an asset from Noun Project (make sure to give attribution to the author!) and added it as an asset of my project. We then give this a flex value of 2 so that it takes up the required space on the screen.
  • Secondly, we define the Text widget to display the welcome message to the user.You’ll notice here that we access the string to be used from our localisation file.
  • Finally, we declare a button widget that will be used to trigger the authentication process. Because of the expanded widgets used above this button, the widget is forced to the bottom of the screen.

You’ll notice that within the onPress() callback for the button there is a function being triggered. We haven’t defined that just yet so let’s take a look at what happens when this is called:

void _authenticateWithGoogle(BuildContext context) async {
final GoogleSignInAccount googleUser = 
await googleSignIn.signIn();
final GoogleSignInAuthentication googleAuth =
await googleUser.authentication;
final FirebaseUser user = await firebaseAuth.signInWithGoogle(
    accessToken: googleAuth.accessToken,
    idToken: googleAuth.idToken,
  Navigator.pop(context, user);

Again, this is something that I outlined in this article here. The only difference now is that once the authentication flow has completed, we pop the login screen out of view, passing back the instance of the user that was retrieved during the authentication process.

Now at this point in the application nothing will actually happen, as we haven’t implemented the screen to be shown when the user is signed in — that will come within the next post 🙂 If you want to test this or check the result that is returned by the authentication flow, then you can attach the debugger and trigger the process to check what data is returned.

When the User instance is retrieved if you need to cache any data for some specific reason then once the user has been fetched that is a good point to do it. However, using the FirebaseAuth instance within your application, you are able to fetch the currently authenticated user using:


However, we won’t need to do this during the authentication flow as we are already listening to the authentication state within our HashtrackApp class.

In this part of the Hashtrack series we’ve setup the Authentication process for our application and created a simple login UI that will allow the user to sign-in to the app. In the next post we’ll cover creating the hashtag list screen to pull in our stored data from Firestore.

Leave a Reply

Your email address will not be published. Required fields are marked *