Flutter Login Tutorial with “flutter_bloc”

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.

Login Flow that we’ll be implementing

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).

bloc high level architecture

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. Events are dispatched and then converted to States.

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.

For example:

  • 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 AuthenticationBloc.

The 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 LoginPage and 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 LoginBloc.

Just like we did for the AuthenticationBloc, we will need to define the LoginState, and LoginEvents. Let’s start with LoginState.

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 LoginEvent class.

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.

Our LoginBloc defines it’s initial state as LoginInitial.

As always, our LoginBloc has to implement mapEventToState.

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 and LoginForm.

The LoginPage widget will serve as our container widget and will provide the necessary dependencies to the LoginForm widget (LoginBloc and AuthenticationBloc).

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 LoginBloc.

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.

Our LoginForm uses the BlocBuilder widget so that it can rebuild whenever there is a new LoginState.

BlocBuilder is a Flutter widget which requires a Bloc and a builder function. 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 main.dart.

Our app has an injected AuthenticationBloc which is makes available to the entire widget tree by using theBlocProvider widget.

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.

Now BlocProvider.of<AuthenticationBloc>(context) in our HomePage and 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 SplashPage, LoginPage, or HomePage based on the current AuthenticationState.

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 SimpleBlocDelegate.

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.

At this point we have a pretty solid login implementation and we have decoupled our presentation layer from the business logic layer by using bloc and flutter_bloc. The full source is available here.

If you enjoyed this exercise as much as I did you can support me by ⭐️the repository, or 👏 for this story.