If you’re working on a Flutter app, odds are you’re going to need to implement login.
In this article we’re going to walk through one possible way to implement a login flow which will end up looking something like this.
The first thing we’re going to need to do is add flutter_bloc as a dependency to our new flutter project. Our
pubspec.yaml should look something like:
Next, we’re going to need to determine how we’re going to manage the state of our application and create the necessary blocs (business logic components).
In this context, a bloc simply takes a stream of events as input and transforms them into a stream of states as output.
Events are the input to a Bloc. They are commonly UI events such as button presses.
dispatched and then converted to
States are the output of a Bloc. Presentation components can listen to the stream of states and redraw portions of themselves based on the given state (see
BlocBuilder for more details).
Check out the bloc package if you haven’t already.
We’re going to need to create a
UserRepository which helps us manage a user's data.
Our user repository is just mocking all of the different implementations for the sake of simplicity but in a real application you might inject a HttpClient as well as something like Flutter Secure Storage in order to request tokens and read/write them to keystore/keychain.
At a high level, we’re going to need to manage the user’s
AuthenticationState which we can represent like:
uninitialized— waiting to see if the user is authenticated or not on app start.
loading— waiting to persist/delete a token
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 loading, the user might be seeing a progress indicator.
- 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
AuthenticationState defined we need to define the
AuthenticationEvents which our bloc will be reacting to.
AppStarted will be dispatched when the Flutter application first loads. It will notify bloc that it needs to determine whether or not there is an existing user.
LoggedIn will be dispatched on a successful login. It will notify the bloc that the user has successfully logged in.
LoggedOut will be dispatched on a successful logout. It will notify the bloc that the user has successfully logged out.
Now that we have defined the events and states related to authentication, we can create our
initialState of the bloc is set to
AuthenticationUninitialized since the first thing our app will need to do is determine whether or not a user is logged in.
mapEventToState is where our bloc converts the incoming events into states the are consumed by the presentational layer.
Now that we have our
AuthenticationBloc fully implemented, let’s get to work on the presentational layer.
The first thing we’ll need is a
SplashPage widget which will serve as our Splash Screen while our bloc determines whether or not a user is logged in.
Next, we will need to create our
HomePage so that we can navigate users there once they have successfully logged in.
You’re probably wondering what
BlocProvider.of<AuthenticationBloc>(context) is doing. We’ll come back to that in a bit but for now all we need to know is that our
HomePage requires an
AuthenticationBloc to be injected.
Next up, we need to create a
LoginForm. Because the LoginForm will have to handle user input (Login Button Pressed) and will need to have some business logic (getting a token for a given username/password), we will need to create a
Just like we did for the
AuthenticationBloc, we will need to define the
LoginEvents. Let’s start with
LoginInitial is the initial state of the LoginForm.
LoginLoading is the state of the LoginForm when we are validating credentials
LoginFailure is the state of the LoginForm when a login attempt has failed.
Now that we have the
LoginState defined let’s take a look at the
LoginButtonPressed will be dispatched when a user pressed the login button. It will notify the bloc that it needs to request a token for the given credentials.
We can now implement our
LoginBloc defines it’s initial state as
As always, our
LoginBloc has to implement
LoginBloc has a dependency on
UserRepository in order to authenticate a user given a username and password. In addition,
LoginBloc has a dependency on
AuthenticationBloc in order to update the
AuthenticationState when a user has entered valid credentials.
Now that we have our
LoginBloc we can start working on
LoginPage widget will serve as our container widget and will provide the necessary dependencies to the
LoginForm widget (
Note that the
LoginPage widget creates the
LoginBloc as part of its state and handles disposing the bloc.
Note that we are using the injected
UserRepository in order to create our
You’re probably still wondering what
BlocProvider.of<AuthenticationBloc>(context) is doing. Don’t worry, we’ll come back to that momentarily but for now all we need to know is that our
LoginForm requires an
AuthenticationBloc and a
LoginBloc to be injected.
Next up, let’s go ahead and create our
LoginForm uses the
BlocBuilder widget so that it can rebuild whenever there is a new
BlocBuilder is a Flutter widget which requires a
Bloc and a
BlocBuilder handles building the widget in response to new states.
BlocBuilder is very similar to
StreamBuilder but has a more simple API to reduce the amount of boilerplate code needed.
There’s not much else going on in the
LoginForm widget so let’s move on to creating our loading indicator.
Now it’s finally time to put it all together and create our main
App widget in
Our app has an injected
AuthenticationBloc which is makes available to the entire widget tree by using the
BlocProvider is a Flutter widget which provides a bloc to its children via
BlocProvider.of(context). It is used as a DI widget so that a single instance of a bloc can be provided to multiple widgets within a subtree.
BlocProvider.of<AuthenticationBloc>(context) in our
LoginPage widget should make more sense. Since we wrapped our MaterialApp within a
BlocProvider<AuthenticationBloc> we can access the instance of our
AuthenticationBloc by using the
BlocProvider.of<AuthenticationBloc>(BuildContext context) static method.
Again, we are using
BlocBuilder in order to react to changes in
AuthenticationState so that we can show the user either the
HomePage based on the current
One added bonus of using bloc is that we can have access to all
Transitions in one place. It's fairly common in large applications to have many blocs managing different parts of the application's state.
If we want to be able to do something in response to all
Transitions we can simply create our own
BlocDelegate which we have named
In order to use our
SimpleBlocDelegate, we just need to tweak our
main function to set the
BlocSupervisor's delegate to our instance of
SimpleBlocDelegate. See the official bloc library documentation for more details.
If you enjoyed this exercise as much as I did you can support me by ⭐️the repository, or 👏 for this story.