Firebase Login with “flutter_bloc”
Hey everyone! In this article we’re going to be building a Flutter Application which integrates with Firebase to to authenticate users as well as allow them to sign up or sign in with Google.
The finished product should look something like this:
If you haven’t already, I highly recommend checking out the basic Flutter Login Tutorial before reading on.
We’ll start off by creating a brand new Flutter project
flutter create flutter_firebase_login
We can then replace the contents of
Notice that we are specifying an assets directory for all of our applications local assets. Create an
assets directory in the root of your project and add the flutter logo asset (which we'll use later).
Then install all of the dependencies:
flutter packages get
Just like in the flutter login tutorial, we’re going to need to create our
UserRepository which will be responsible for abstracting the underlying implementation for how we authenticate and retrieve user information.
user_repository.dart and get started.
We can start by defining our
UserRepository class and implementing the constructor. You can immediately see that the
UserRepository will have a dependency on both
GoogleSignIn are not injected into the
UserRepository, then we instantiate them internally. This allows us to be able to inject mock instances so that we can easily test the
The first method we’re going to implement we will call
signInWithGoogle and it will authenticate the user using the
Next, we’ll implement a
signInWithCredentials method which will allow users to sign in with their own credentials using
Up next, we need to implement a
signUp method which allows users to create an account if they choose not to use Google Sign In.
We need to implement a
signOut method so that we can give users the option to logout and clear their profile information from the device.
Lastly, we will need two additional methods:
getUser to allow us to check if a user is already authenticated and to retrieve their information.
getUser is only returning the current user's email address for the sake of simplicity but we can define our own User model and populate it with a lot more information about the user in more complex applications.
user_repository.dart should look like this:
Next up, we’re going to build our
AuthenticationBloc which will be responsible for handling the
AuthenticationState of the application in response to
We need to determine how we’re going to manage the state of our application and create the necessary blocs (business logic components).
At a high level, we’re going to need to manage the user’s Authentication State. A user’s authentication state can be one of the following:
- uninitialized — waiting to see if the user is authenticated or not on app start.
- authenticated — successfully authenticated
- unauthenticated — not authenticated
Each of these states will have an implication on what the user sees.
- if the authentication state was uninitialized, the user might be seeing a splash screen
- if the authentication state was authenticated, the user might see a home screen.
- if the authentication state was unauthenticated, the user might see a login form.
It’s critical to identify what the different states are going to be before diving into the implementation.
Now that we have our authentication states identified, we can implement our
Create a folder/directory called
authentication_bloc and we can create our authentication bloc files.
│ ├── authentication_bloc.dart
│ ├── authentication_event.dart
│ ├── authentication_state.dart
│ └── bloc.dart
equatable package is used in order to be able to compare two instances of
AuthenticationState. By default,
== returns true only if the two objects are the same instance.
toString is overridden to make it easier to read an
AuthenticationState when printing it to the console or in
Since we’re using
Equatableto allow us to compare different instances of
AuthenticationStatewe need to pass any properties to the superclass. Without
super([displayName]), we will not be able to properly compare different instances of
Now that we have our
AuthenticationState defined we need to define the
AuthenticationEvents which our
AuthenticationBloc will be reacting to.
We will need:
AppStartedevent to notify the bloc that it needs to check if the user is currently authenticated or not.
LoggedInevent to notify the bloc that the user has successfully logged in.
LoggedOutevent to notify the bloc that the user has successfully logged out.
Authentication Barrel File
Before we get to work on the
AuthenticationBloc implementation, we will export all authentication bloc files from our
authentication_bloc/bloc.dart barrel file. This will allow us import the
AuthenticationState with a single import later on.
Now that we have our
AuthenticationEvents defined, we can get to work on implementing the
AuthenticationBloc which is going to manage checking and updating a user's
AuthenticationState in response to
We’ll start off by creating our
Note: Just from reading the class definition, we already know this bloc is going to be converting
AuthenticationBloc has a dependency on the
We can start by overriding
initialState to the
Now all that’s left is to implement
We created separate private helper functions to convert each
AuthenticationEvent into the proper
AuthenticationState in order to keep
mapEventToState clean and easy to read.
Note: We are using
yield* (yield-each) in
mapEventToState to separate the event handlers into their own functions.
yield* inserts all the elements of the subsequence into the sequence currently being constructed, as if we had an individual yield for each element.
authentication_bloc.dart should now look like this:
Now that we have our
AuthenticationBloc fully implemented, let’s get to work on the presentational layer.
We’ll start by removing everything from out
main.dart and implementing our main function.
Next we need to implement our
Appwill be a
StatefulWidgetbecause it will need to manage our
We create an instance of
UserRepository in our
_AppState class and then inject it into our
initState. Since we created the
_AppState we need to clean up after ourselves and dispose of it in
We are using
BlocProvider in order to make our
_authenticationBloc instance available to the entire
Widget sub-tree. We are also using
BlocBuilder in order to render UI based on the
So far we don’t have any widgets to render but we’ll come back to this once we make our
Before we get too far along, it’s always handy to implement our own
BlocDelegate which allows us to override
onError and will help us see all bloc state changes (transitions) and errors in one place!
simple_bloc_delegate.dart and let's quickly implement our own delegate.
Now we can hook up our
BlocDelegate in our
Next, we’ll need to make a
SplashScreen widget which will be rendered while our
AuthenticationBloc determines whether or not a user is logged in.
splash_screen.dart and implement it!
As you can tell, this widget is super minimal and you would probably want to add some sort of image or animation in order to make it look nicer. For the sake of simplicity, we’re just going to leave it as is.
Now, let’s hook it up to our
Now whenever our
AuthenticationBloc has a
Uninitialized we will render our
Next, we will need to create our
HomeScreen so that we can navigate users there once they have successfully logged in. In this case, our
HomeScreen will allow the user to logout and also will display their current name (email).
home_screen.dart and get started.
HomeScreen is a
StatelessWidget that requires a
name to be injected so that it can render the welcome message. It also uses
BlocProvider in order to access the
BuildCOntext so that when a user pressed the logout button, we can dispatch the
Now let’s update our
App to render the
HomeScreen if the
It’s finally time to start working on the login flow. We’ll start by identifying the different
LoginStates that we'll have.
login directory and create the standard bloc directory and files.
│ ├── login
│ │ ├── bloc
│ │ │ ├── bloc.dart
│ │ │ ├── login_bloc.dart
│ │ │ ├── login_event.dart
│ │ │ └── login_state.dart
login/bloc/login_state.dart should look like:
The states we’re representing are:
empty is the initial state of the LoginForm.
loading is the state of the LoginForm when we are validating credentials
failure is the state of the LoginForm when a login attempt has failed.
success is the state of the LoginForm when a login attempt has succeeded.
We have also defined a
copyWith and an
update function for convenience (which we'll put to use shortly).
Now that we have the
LoginState defined let’s take a look at the
login/bloc/login_event.dart and let's define and implement our events.
The events we defined are:
EmailChanged - notifies the bloc that the user has changed the email
PasswordChanged - notifies the bloc that the user has changed the password
Submitted - notifies the bloc that the user has submitted the form
LoginWithGooglePressed - notifies the bloc that the user has pressed the Google Sign In button
LoginWithCredentialsPressed - notifies the bloc that the user has pressed the regular sign in button.
Login Barrel File
Before we implement the
LoginBloc, let's make sure our barrel file is done so that we can easily import all Login Bloc related files with a single import.
It’s time to implement our
LoginBloc. As always, we need to extend
Bloc and define our
initialState as well as
Note: We’re overriding
transform in order to debounce the
PasswordChanged events so that we give the user some time to stop typing before validating the input.
We are using a
Validators class to validate the email and password which we're going to implement next.
validators.dart and implement our email and password validation checks.
There’s nothing special going on here. It’s just some plain old Dart code which uses regular expressions to validate the email and password. At this point, we should have a fully functional
LoginBloc which we can hook up to the UI.
Now that we’re finished the
LoginBloc it's time to create our
LoginScreen widget which will be responsible for creating and disposing the
LoginBloc as well as providing the Scaffold for our
login/login_screen.dart and let's implement it.
Again, we are extending
StatefulWidget so that we can initialize the
initState and dispose it in the
dispose override. You'll also notice that we are using
BlocProvider again in order to make the
_loginBloc instance available to all widgets within the sub-tree.
At this point, we need to implement the
LoginForm widget which will be responsible for displaying the form and submission buttons in order for a user to authenticate his/her self.
login/login_form.dart and let's build out our form.
LoginForm widget is a
StatefulWidget because it needs to maintain it's own
TextEditingControllers for the email and password input.
We use a
BlocListener widget in order to execute one-time actions in response to state changes. In this case, we are showing different
SnackBar widgets in response to a pending/failure state. In addition, if the submission is successful, we use the
listener method to notify the
AuthenticationBloc that the user has successfully logged in.
Tip: Check out the BlocListener Recipe for more details.
We use a
BlocBuilder widget in order to rebuild the UI in response to different
Whenever the email or password changes, we dispatch an event to the
LoginBloc in order for it to validate the current form state and return the new form state.
Note: We’re using
Image.asset to load the flutter logo from our assets directory.
At this point, you’ll notice we haven’t implemented
CreateAccountButton so we'll do those next.
login/login_button.dart and let's quickly implement our
There’s nothing special going on here; just a
StatelessWidget which has some styling and an
onPressed callback so that we can have a custom
VoidCallback whenever the button is pressed.
Google Login Button
login/google_login_button.dart and let's get to work on our Google Sign In.
Again, there’s not too much going on here. We have another
StatelessWidget; however, this time we are not exposing an
onPressed callback. Instead, we're handling the onPressed internally and dispatching the
LoginWithGooglePressed event to our
LoginBloc which will handle the Google Sign In process.
Note: We’re using font_awesome_flutter for the cool google icon.
Create Account Button
The last of the three buttons is the
CreateAccountButton. Let's create
login/create_account_button.dart and get to work.
In this case, again we have a
StatelessWidget and again we're handling the
onPressed callback internally. This time, however, we're pushing a new route in response to the button press to the
RegisterScreen. Let's build that next!
Just like with login, we’re going to need to define our
RegisterStates before proceeding.
register directory and create the standard bloc directory and files.
│ ├── register
│ │ ├── bloc
│ │ │ ├── bloc.dart
│ │ │ ├── register_bloc.dart
│ │ │ ├── register_event.dart
│ │ │ └── register_state.dart
register/bloc/register_state.dart should look like:
RegisterState is very similar to the
LoginState and we could have created a single state and shared it between the two; however, it's very likely that the Login and Register features will diverge and in most cases it's best to keep them decoupled.
Next, we’ll move on to the
register/bloc/register_event.dart and let's implement our events.
Register Barrel File
Again, just like with login, we need to create a barrel file to export our register bloc related files.
bloc.dart in our
register/bloc directory and export the three files.
Now, let’s open
register/bloc/register_bloc.dart and implement the
Just as before, we need to extend
mapEventToState. Optionally, we are overriding
transform again so that we can give users some time to finish typing before we validate the form.
Now that the
RegisterBloc is fully functional, we just need to build out the presentation layer.
Similar to the
RegisterScreen will be a
StatefulWidget responsible for initializing and disposing the
RegisterBloc. It will also provide the Scaffold for the
register/register_screen.dart and let's implement it.
Next, let’s create the
RegisterForm which will provide the form fields for a user to create his/her account.
register/register_form.dart and let's build it.
Again, we need to manage
TextEditingControllers for the text input so our
RegisterForm needs to be a
StatefulWidget. In addition, we are using
BlocListener again in order to execute one-time actions in response to state changes such as showing
SnackBar when the registration is pending or fails. We are also dispatching the
LoggedIn event to the
AuthenticationBloc if the registration was a success so that we can immediately log the user in.
Note: We’re using
BlocBuilder in order to make our UI respond to changes in the
Let’s build our
RegisterButton widget next.
register/register_button.dart and let's get started.
Very similar to how we setup the
RegisterButton has some custom styling and exposes a
VoidCallback so that we can handle whenever a user presses the button in the parent widget.
All that’s left to do is update our
App widget in
main.dart to show the
LoginScreen if the
At this point we have a pretty solid login implementation using Firebase and we have decoupled our presentation layer from the business logic layer by using the Bloc Library.
The full source for this example can be found here.
If you enjoyed this exercise as much as I did you can support me by ⭐️the repository, or 👏 for this story.