Flutter bloc authentication flow

Erik
Dreamwod tech
Published in
4 min readFeb 22, 2021

Part 2 in the complete flutter app walkthrough. Designing an onboarding and authentication flow with the bloc framework.

This is the second post in a series on how the Dreamwod app is built. The Dreamwod app is an iOS and Android app for CrossFit athletes where they can track their progress and for CrossFit gyms that want their athletes to engage in their community. We have previously described the components and package used to build a “details page” in the app. This post will take a step back and instead look at the onboarding flow and how the bloc pattern can be used to structure the logic around onboarding and authentication.

The series contains (at least) the following posts.

  1. Components used for building the details page.
  2. Flutter bloc authentication flow (this post).
  3. Push notifications setup (available later).
  4. Components used for building the profile page (available later).
  5. Integrating Stripe for payments (available later).
  6. Build and publish with Fastlane (available later).

Prerequisites

Bloc pattern

Flutter_bloc is a package that can be used to separate the presentation logic from the business logic. It is typically achieved by sending events into a bloc and have the bloc update the app state. A lot of good examples are available here.

Secure storage

Our app uses JWT tokens for authentication. The JWT is sensitive information and should be stored in a secure way. We are using the flutter_secure_storage package for this and have wrapped the read and write logic into a repository which the app can use.

Repository for storing JWT token and email

Onboarding and authentication flow

Overview of the onboarding flow

The above state diagram describes an overview of how the authentication flow works. Our app is wrapped in an AuthenticationBloc and the AppStarted event is fired directly at the start of the app.

The main function that initializes authentication bloc and app

The logic in the Authentication bloc is pretty simple, events are handled and the state is changed.

Authentication bloc

Some things are worth pointing out.

  1. This bloc is also a good place to set up other things like error logging towards Sentry, Firebase Analytics, register Firebase push notifications, etc.
  2. The start state is AuthenticationUninitialized. We use this special state so that a loading spinner can be displayed when the AppStarted event is processed.
  3. We check if the app is started for the first time after install/re-install. One problem with using the flutter_secure_storage package is that information is stored in the iOS keychain. Information in the key chain will be available even after the app is re-installed. That is not something that we want so we have a check that will clear everything after an install/re-install. The information if it’s the first run is stored in the SharedPreferences. SharedPreferences are deleted when the app is uninstalled.
  4. This bloc doesn’t handle the logic for login. That is handled by a SignInBloc which emits a LoginEvent with the JWT token and the email of the user to the AuthenticationBloc. See the example below. This separates the logic in a good way between different blocs and views.
Sending event from SignInBloc to the AuthenticationBloc

Updating the UI on different states

Different states map to different screens. The AuthenticationAuthenticated state is the only state that will display the app for authenticated users.

Switching between different screens

Testing the flows

Using a global observer on the blocs is a must to be able to follow between events and state transitions. The observer below will log all events and states and make it easier to follow the transitions. Can be added with Bloc.observer = LoggingBlocDelegate();

Bloc observer for logging transitions

Example 1. Starting the app without a JWT Token.

Transition { currentState: Instance of ‘AuthenticationUninitialized’, event: Instance of ‘AppStarted’, nextState: Instance of ‘AuthenticationUnauthenticated’}

Straight forward, the event AppStarted is changing the state to AuthenticationUnauthenticated since the user isn’t authenticated.

Example 2. Log in with email/password

Transition { currentState: Instance of ‘SignInInitial’, event: Instance of ‘SignInButtonPressed’, nextState: Instance of ‘SignInLoading’ } (SignInBloc)

Transition { currentState: Instance of ‘SignInLoading’, event: Instance of ‘SignInButtonPressed’, nextState: Instance of ‘SignInSuccess’ } (SignInBloc)

Transition { currentState: Instance of ‘AuthenticationLoading’, event: Instance of ‘LoggedIn’, nextState: Instance of ‘AuthenticationAuthenticated’ } (AuthenticationBloc)

A bit more complicated. The first state transition happens when the user taps the SignIn button. The second one is the transition when the password is verified in the backend. The third transition is when the AuthenticationBloc processed the event that the user is logged in and changes the state.

Example 3. Logout

Transition { currentState: Instance of ‘AuthenticationAuthenticated’, event: Instance of ‘LoggedOut’, nextState: Instance of ‘AuthenticationLoggingOut’ }

Transition { currentState: Instance of ‘AuthenticationLoggingOut’, event: Instance of ‘LoggedOut’, nextState: Instance of ‘AuthenticationUnauthenticated’ }

State changes from AuthenticationAuthenticated >AuthenticationLoggingOut > AuthenticationUnauthenticated. It can take a second or two to log out so the state AuthenticationLoggingOut is used for displaying a loading spinner.

Conclusions

Using the bloc framework can be a good approach when designing a flow with different states, for example an authentication and onboarding flow. Some key takeaways.

  • Don’t try to add too much logic into the same bloc, better to separate into several blocs.
  • Wrap the app in the main bloc responsible for authentication and use that to change between authenticated and unauthenticated states.
  • Use intermediate loading states for operations that take longer time.
  • Events can be added directly in the initialization of blocs with the ..add(MyEvent); syntax.
  • Don't forget to test edge cases and error scenarios. The authentication flow must be rock solid!

Good luck! 💪

--

--

Erik
Dreamwod tech

Developer, backend, frontend, ML. Likes crossfit and training. Building on the app dreamwod.app.