Flutter — A Clean Architecture implementation proposal

Gáyan Justo de Moraes
4 min readMay 13, 2021

--

repository: https://github.com/gjmcodes/flutter_clean_template

The main purpose of this template is to bring my own view and implementation on top of the Clean Architecture written by Bob Martin. The final goal is to have the project working with fully isolated layers, internationalization ( i18n), returning validations from the domain back to the UI, and features implemented in a folder structure which will make it easy to turn into modules in the future.

Core Layer

The whole application has a major layer named Core which responsabilities are to declare abstract objects, shared and common implementations from base classes to common widgets.

Each feature will replicate the same folder structure which consists of:

Domain Layer

The domain layer is responsible for holding business logic through entities and use cases. Therefore it’s the layer responsible for telling how an application must work accordingly with it’s business rules and known objects. This layer must not have any reference of objects that are related to other feature layers such as ui, infra nor blocs. It may declare abstract classes (or interfaces for those coming from TypeScript or C#) that will be implemented in any other layer. In other words, the Domain Layer cannot depend on any other layer apart from shared objects in the core that will help to compose the domain.

Use Cases

Use Cases are responsible for containing application and business validation for a specific request/flow.

For Use Cases implementation I decided to use 4 different objects to deal with the use case workflow.

  • Use Case Model: Working as a DTO for a specific Use Case.
  • Use Case Request: An abstract class which enforces how an Use Case must be implemented in a Handler
  • Use Case Handler: A class which will work as the orchestrator of the use case. It’s responsability is to call any required entity from the domain model and return to it’s caller whether the Use Case Model sent is valid or not.
  • Use Case Response: Another DTO which will be returned to the Use Case Handler caller with information that tells whether the data sent was valid or not.

For this template we have the entity “Resource” and an Use Case implementation that holds logic and validation to add a new resource, therefore: AddResourceUseCase.

The Use Case itself represents the DTO ( Data Transfer Object) to send a request to its Handler which will be responsible for handling all logic such as: validating the entity, calling events in case of success or failure, returning results for its caller.

For every Use Case we have a Handler on top of it. In this template we have a single Handler for context which implements all UseCases logic. This handler is ResourceUseCaseHandler.

Validations

For validations we’re using the specifications pattern which will be called by entities accordingly to exposed functions for specific flows/validations. A specification is composed by multiple business rules that can be reused by several specifications with different validation responsabilities.

In our case we have the new_resource.specification.dart file which groups all business rules to validate whether a new resource can be created or not.

Returning Validations to the Presentation Layer

To reveal a validation message for a field we must extend a BloC specific for forms that inherits “BaseFormBloc”. Once extending this class, its implementation will required an override of the function “setValidations()”

In the Resources example above we can see we’re providing a value for a Map of the type Map<String, StreamController>. The key to this Map is the located in the constants file at the root of our feature domain folder.

This same Key is also used as the Key to return our Business Validation mentioned in the previous topic.

Both Map and validations will know each other through their common keys. At the implementation of our BaseFormBloc we can call the function addValidations in case a Validation Result returns invalid from an Use Case Handler.

Once triggered, the stream will send the message to our FormTextInput which must be listening to streams defined to our BaseFormBloc implementations streams.

--

--

Gáyan Justo de Moraes
Gáyan Justo de Moraes

Written by Gáyan Justo de Moraes

C# / .NET developer eager to jump into the mobile development world! (also a Godot enthusiast!)

Responses (1)