Flutter Hexagonal Architecture. Part 2. Authentication.

Nikita Lapin
6 min readJun 10, 2022

--

Article title as image

In the previous part, we talked about the basic concepts of Hexagonal Clean Architecture.

Hexagonal Architecture overview
Architecture overview (Main modules)

Enough of theory, let’s do some practice.

We will try to implement Authentication using Firebase with an email and password provider. Our goal is to see how to apply Hexagonal Architecture to the enterprise case. Here are our use cases:

  • As a user, I’d like to have the ability to log in with email and password.
  • As a user, I’d like to have the ability to log out from a previously logged-in profile.
  • As a user, I’d like to be logged in every time I open the app if I logged in before.

Part 1. Introduction.

Part 2. Authentication. You are here.

Part 3. Fetching data. Coming soon…

Part 4. Conclusion. Coming soon…

DISCLAIMER: In this case, we are coding a simple application, but Hexagonal Architecture may be a perfect fit for large enterprise applications. If your application has simple CRUD commands, maybe it’s overhead for you. So be skeptical about applying this while reading further.

Ok, let’s start from the heart of the app — Entity.

Entity

Entities encapsulate Enterprise wide business rules. An entity can be an object with methods, or it can be a set of data structures and functions. It doesn’t matter so long as the entities could be used by many different applications in the enterprise.

If you don’t have an enterprise, and are just writing a single application, then these entities are the business objects of the application. They encapsulate the most general and high-level rules. They are the least likely to change when something external changes. For example, you would not expect these objects to be affected by a change to page navigation, or security. No operational change to any particular application should affect the entity layer.

The Clean Code Blog by Robert C. Martin (Uncle Bob)

In the authentication use case, there are no specific domain rules that will be implemented in the current sample. You can update passwords and email, and apply other business rules here. The extended domain model will be implemented in the following articles.

user.dart

Use Case

Why do we need UseCase?

According to the Robert C. Martin (Uncle Bob) Use Case is:

The software in this layer contains application specific business rules. It encapsulates and implements all of the use cases of the system. These use cases orchestrate the flow of data to and from the entities, and direct those entities to use their enterprise wide business rules to achieve the goals of the use case.

We do not expect changes in this layer to affect the entities. We also do not expect this layer to be affected by changes to externalities such as the database, the UI, or any of the common frameworks. This layer is isolated from such concerns.

We do, however, expect that changes to the operation of the application will affect the use-cases and therefore the software in this layer. If the details of a use-case change, then some code in this layer will certainly be affected.

Use Cases usually do such things:

  1. Take input
  2. Validate business rules
  3. Manipulate model state
  4. Return output

Don’t confuse input and business validation. According to the book “Get Your Hands Dirty on Clean Architecture” by Tom Hombergs

Validating a business rule requires access to the current state of the domain model while validating input does not.

Input validation is not part of business rules, but it should be done inside the application core. There are several reasons to do so: we shouldn’t trust adapters to validate their input and it will reduce the amount of repeated code in case several adapters call the same Use Case.

Login Command Dart class
login_command.dart

There are several use-cases that we will implement. The first one is Log in use-case.

Login Use Case class
login_use_case.dart

UseCase interface (abstract class in the case of Dart) is the input port in terms of Hexagonal Architecture. The service that implements abstract classes is UseCase itself.

If you can Log in to the system there should be functionality to log out from the session:

Log out Use Case class
logout_use_case.dart

If you open the app with the cold boot (the app is not loaded from the RAM but loads to the memory) you should check if the user is logged in. To do so let’s create a separate UseCase:

Load User use case class
load_user_use_case.dart

We are using Stream<User?> to be notified if the user logged out from the app or changed.

Let’s see in which way to implement such use-cases:

Auth Service that implements several use cases. Login, logout, load user use cases
auth_service.dart

As we saw in the picture of the Hexagonal Architecture UseCases communicate with the outer world through the ports.

In Flutter world UI triggers communication with UseCase through the BLoC library (you can use the state management tool you like).

UI Adapter

The outermost layer is generally composed of frameworks and tools such as the Database, the Web Framework, Flutter Framework etc. Generally you don’t write much code in this layer other than glue code that communicates to the next circle inwards.

This layer is where all the details go. The Flutter is a detail. The database is a detail. We keep these things on the outside where they can do little harm.

👆 I’ve selected text bold I changed to have an understanding of how to apply Clean Architecture to the Flutter.

We will discuss BLoC implementation, not widgets. My suggestion is to have one BLoC for each screen that should interact with application business logic. We have very primitive BLoC classes that describe interaction with User Interface. Let’s discuss each element separately:

The authentication event describes how the user interacts with the app. If the user clicks on the Login button UI fires an event to the Authentication BLoC. The same for the log-out functionality.

BLoC events
auth_event.dart

States express the current situation inside the app.

  • An Initial State is needed to show the splash screen and to indicate that we need to clarify the login status.
  • AuthLoggedInState shows us that the user is logged in and we can show the Profile Details page.
  • UnauthState is needed to indicate that we have to redirect the user to the login page.
BLoC states
auth_state.dart

The presentation layer (BLoC) is placed outside of the application core so it interacts with the application through input ports (use case interfaces).

Authentication bloc class
auth_bloc.dart

Folder structure (packages structure)

Last but not least. We have so many elements, such as use cases, input and output ports, and incoming (BLoC) and outgoing (Network repositories, Firebase Auth package) adapters in our Hexagonal Architecture. So we should find a way to structure them into our project:

folder structure with lob as root folder

P.S.

Thank you all for reading. That was the second article in a series of Hexagonal Architecture usage for the Flutter enterprise project. If you liked this article do not forget to clap 👏 and add this to your list or share it with your colleagues. Some useful links are listed below:

Telegram channel, where I write and repost some useful articles and express my own opinion on some Mobile/Backend technologies.

GitHub project:

--

--