0% found this document useful (0 votes)
160 views38 pages

Flutter Clean Architecture The

Arquitetura Flutter Limpo e claro!

Uploaded by

vadozam
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
160 views38 pages

Flutter Clean Architecture The

Arquitetura Flutter Limpo e claro!

Uploaded by

vadozam
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Flutter Clean Architecture

Flutter is an open-source UI software development kit created


by Google. It is used to develop applications for Android, iOS,
Windows, Mac, Linux, Google Fuchsia and the web from a single
codebase.
Flutter framework architecture
The major components of Flutter include:
Dart platform
Flutter engine
Foundation library
Design-specific widgets.

Clean Architecture. Introduction


Architecture means the overall design of the project. It's the
organization of the code into classes or files or components or
modules. And it's how all these groups of code relate to each other.
The architecture defines where the application performs its core
functionality and how that functionality interacts with things like the
database and the user interface.
Clean architecture refers to organizing the project so that it's easy to
understand and easy to change as the project grows. This doesn't
happen by chance. It takes intentional planning.

Clean Architecture is architecture based on the book and blog by


Uncle Bob. It is a combination of concepts taken from the Onion
Architecture and other architectures. The main focus of the
architecture is separation of concerns and scalability. It consists of
four main modules: App , Domain , Data , and Device .

Let’s touch theory first. Over the last several years we’ve seen a
whole range of ideas regarding the architecture of systems. These
include:
Hexagonal Architecture (a.k.a. Ports and Adapters) by Alistair
Cockburn and adopted by Steve Freeman, and Nat Pryce in their
wonderful book Growing Object Oriented Software
Onion Architecture by Jeffrey Palermo
Screaming Architecture from a blog of mine last year
DCI from James Coplien, and Trygve Reenskaug.
BCE by Ivar Jacobson from his book Object Oriented Software
Engineering: A Use-Case Driven Approach
Though these architectures all vary somewhat in their details, they
are very similar. They all have the same objective, which is the
separation of concerns. They all achieve this separation by dividing
the software into layers. Each has at least one layer for business
rules, and another for interfaces.

Each of these architectures produce systems that are:


1. Independent of Frameworks. The architecture does not
depend on the existence of some library of feature laden
software. This allows you to use such frameworks as tools,
rather than having to cram your system into their limited
constraints.
2. Testable. The business rules can be tested without the UI,
Database or any other external element.
3. Independent of UI. The UI can change easily, without
changing the rest of the system.
4. Independent of Database. Your business rules are not bound
to the database.
5. Independent of any external agency. In fact, your business
rules simply don’t know anything at all about the outside
world.
The diagram at the end of this book is an attempt at integrating all
these architectures into a single actionable idea.
The Dependency Rule
The concentric circles represent different areas of software. In
general, the further in you go, the higher level the software becomes.
The outer circles are mechanisms. The inner circles are policies.
The overriding rule that makes this architecture work is The
Dependency Rule. This rule says that source code
dependencies can only point inwards. Nothing in an inner circle can
know anything at all about something in an outer circle. In particular,
the name of something declared in an outer circle must not be
mentioned by the code in an inner circle. That includes, functions,
classes. variables, or any other named software entity.
By the same token, data formats used in an outer circle should not
be used by an inner circle, especially if those formats are generated
by a framework in an outer circle. We don’t want anything in an outer
circle to impact the inner circles.
Entities
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.
Use Cases
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.
Interface Adapters
The software in this layer is a set of adapters that convert data from
the format most convenient for the use cases and entities, to the
format most convenient for some external agency such as the
Database or the Web. It is this layer, for example, that will wholly
contain the MVC architecture of a GUI. The Presenters, Views, and
Controllers all belong in here. The models are likely just data
structures that are passed from the controllers to the use cases, and
then back from the use cases to the presenters and views.
Similarly, data is converted, in this layer, from the form most
convenient for entities and use cases, into the form most convenient
for whatever persistence framework is being used. i.e. The
Database. No code inward of this circle should know anything at all
about the database. If the database is a SQL database, then all the
SQL should be restricted to this layer, and in particular to the parts of
this layer that have to do with the database.
Also, in this layer is any other adapter necessary to convert data
from some external form, such as an external service, to the internal
form used by the use cases and entities.
Frameworks
The outermost layer is generally composed of frameworks and tools
such as the Database, the Web 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 Web is a detail. The
database is a detail. We keep these things on the outside where they
can do little harm.
Only Four Circles?
No, the circles are schematic. You may find that you need more than
just these four. There’s no rule that says you must always have just
these four. However, The Dependency Rule always applies. Source
code dependencies always point inwards. As you move inwards the
level of abstraction increases. The outermost circle is low level
concrete detail. As you move inwards the software grows more
abstract, and encapsulates higher level policies. The inner most
circle is the most general.
Crossing boundaries.
At the lower right of the diagram is an example of how we cross the
circle boundaries. It shows the Controllers and Presenters
communicating with the Use Cases in the next layer. Note the flow of
control. It begins in the controller, moves through the use case, and
then winds up executing in the presenter. Note also the source code
dependencies. Each one of them points inwards towards the use
cases.
We usually resolve this apparent contradiction by using
the Dependency Inversion Principle. In a language like Java, for
example, we would arrange interfaces and inheritance relationships
such that the source code dependencies oppose the flow of control
at just the right points across the boundary.
For example, consider that the use case needs to call the presenter.
However, this call must not be direct because that would violate The
Dependency Rule: No name in an outer circle can be mentioned by
an inner circle. So, we have the use case call an interface (Shown
here as Use Case Output Port) in the inner circle, and have the
presenter in the outer circle implement it.
The same technique is used to cross all the boundaries in the
architectures. We take advantage of dynamic polymorphism to
create source code dependencies that oppose the flow of control so
that we can conform to The Dependency Rule no matter what
direction the flow of control is going in.
What data crosses the boundaries?
Typically, the data that crosses the boundaries is simple data
structures. You can use basic structs or simple Data Transfer objects
if you like. Or the data can simply be arguments in function calls. Or
you can pack it into a HashMap, or construct it into an object. The
important thing is that isolated, simple, data structures are passed
across the boundaries. We don’t want to cheat and pass Entities or
Database rows. We don’t want the data structures to have any kind
of dependency that violates The Dependency Rule.
For example, many database frameworks return a convenient data
format in response to a query. We might call this a RowStructure. We
don’t want to pass that row structure inwards across a boundary.
That would violate The Dependency Rule because it would force an
inner circle to know something about an outer circle.
So, when we pass data across a boundary, it is always in the form
that is most convenient for the inner circle.
Conclusion
Conforming to these simple rules is not hard, and will save you a lot
of headaches going forward. By separating the software into layers,
and conforming to The Dependency Rule, you will create a system
that is intrinsically testable, with all the benefits that implies. When
any of the external parts of the system become obsolete, like the
database, or the web framework, you can replace those obsolete
elements with a minimum of fuss.

The Dependency Rule


Source code dependencies only point inwards. This means
inward modules are neither aware of nor dependent on outer
modules. However, outer modules are both aware of and dependent
on inner modules. Outer modules represent the mechanisms by
which the business rules and policies (inner modules) operate. The
more you move inward; the more abstraction is present. The outer
you move the more concrete implementations are present. Inner
modules are not aware of any classes, functions, names, libraries,
etc. present in the outer modules. They simply represent rules and
are completely independent from the implementations.

Layers
Domain
The Domain module defines the business logic of the application. It
is a module that is independent from the development platform i.e. it
is written purely in the programming language and does not contain
any elements from the platform. In the case
of Flutter , Domain would be written purely in Dart without
any Flutter elements. The reason for that is that Domain should
only be concerned with the business logic of the application, not with
the implementation details. This also allows for easy migration
between platforms, should any issues arise.

Contents of Domain
Domain is made up of several things.
Entities
Enterprise-wide business rules
Made up of classes that can contain methods
Business objects of the application
Used application-wide
Least likely to change when something in the
application changes
Usecases
Application-specific business rules
Encapsulate all the usecases of the application
Orchestrate the flow of data throughout the app
Should not be affected by any UI changes whatsoever
Might change if the functionality and flow of
application change
Repositories
Abstract classes that define the expected functionality
of outer layers
Are not aware of outer layers, simply define expected
functionality
E.g. The Login usecase expects
a Repository that has login functionality
Passed to Usecases from outer layers
Domain represents the inner-most layer. Therefore, it the most
abstract layer in the architecture.

App
App is the layer outside Domain . App crosses the boundaries of
the layers to communicate with Domain . However,
the Dependency Rule is never violated.
Using polymorphism , App communicates with Domain using
inherited class: classes that implement or extend
the Repositories present in the Domain layer.
Since polymorphism is used, the Repositories passed
to Domain still adhere to the Dependency Rule since as far
as Domain is concerned, they are abstract. The implementation is
hidden behind the polymorphism .

Contents of App

Since App is the presentation layer of the application, it is the most


framework-dependent layer, as it contains the UI and the event
handlers of the UI. For every page in the application, App defines
at least 3 classes: a Controller , a Presenter , and a View .
View
Represents only the UI of the page. The View builds the
page's UI, styles it, and depends on the Controller to handle its
events. The View has-a Controller .
In the case of Flutter
The View is comprised of 2 classes
One that extends View , which would be the
root Widget representing the View
One that extends ViewState with the template
specialization of the other class and its Controller .
The ViewState contains the build method, which is
technically the UI
StatefulWidget contains the State as per Flutter
The StatefulWidget only serves to pass arguments to
the State from other pages such as a title etc.. It only
instantiates the State object (the ViewState ) and provides it
with the Controller it needs.
The StatefulWidget has-a State object (the ViewState )
which has-a Controller
In summary, both the StatefulWidget and the State are
represented by a View and ViewState of the page.
The ViewState class maintains a GlobalKey that can be
used as a key in its scaffold. If used, the Controller can easily
access it via getState() in order to show snackbars and other
dialogs. This is helpful but optional.
Controller
Every ViewState has-a Controller . The Controller provides
the needed member data of the ViewState i.e. dynamic data.
The Controller also implements the event-handlers of
the ViewState widgets, but has no access to
the Widgets themselves. The ViewState uses
the Controller , not the other way around. When
the ViewState calls a handler from
the Controller , refreshUI() can be called to update the view.
Every Controller extends the Controller abstract class, which
implements WidgetsBindingObserver . Every Controller class
is responsible for handling lifecycle events for the View and
can override:
void onInActive()
void onPaused()
void onResumed()
void onDetatched()
void onDidPop()
etc..
Also, every Controller has to implement initListeners() that
initializes the listeners for the Presenter for consistency.
The Controller has-a Presenter . The Controller will pass
the Repository to the Presenter , which it communicate later
with the Usecase . The Controller will specify what listeners
the Presenter should call for all success and error events as
mentioned previously. Only the Controller is allowed to obtain
instances of a Repository from the Data or Devic e module
in the outermost layer.
The Controller has access to the ViewState and can refresh
the UI via refreshUI().

Presenter
Every Controller has-a Presenter .
The Presenter communicates with the Usecase as
mentioned at the beginning of the App layer.
The Presenter will have members that are functions, which are
optionally set by the Controller and will be called if set upon
the Usecase sending back data, completing, or erroring.
The Presenter is comprised of two classes
Presenter e.g. LoginPresenter
Contains the event-handlers set by the Controller
Contains the Usecase to be used
Initializes and executes the usecase with
the Observer<T> class and the appropriate arguments.
E.g. with username and password in the case of
a LoginPresenter
A class that implements Observer<T>
Has reference to the Presenter class. Ideally, this
should be an inner class but Dart does not yet support
them.
Implements 3 functions
onNext(T)
onComplete()
onError()
These 3 methods represent all possible outputs of
the Usecase
If the Usecase returns an object, it will be passed
to onNext(T).
If it errors, it will call onError(e).
Once it completes, it will call onComplete().
These methods will then call the corresponding methods
of the Presenter that are set by the Controller . This
way, the event is passed to the Controller , which can
then manipulate data and update the ViewState
Extra
Utility classes (any commonly used functions like timestamp
getters etc..)
Constants classes ( const strings for convenience)
Navigator (if needed)

Data
Represents the data-layer of the application. The Data module,
which is a part of the outermost layer, is responsible for data
retrieval. This can be in the form of API calls to a server, a local
database, or even both.

Contents of Data
Repositories
Every Repository should implement Repository fro
m the Domain layer.
Using polymorphism , these repositories from the
data layer can be passed across the boundaries of
layers, starting from the View down to
the Usecases through
the Controller and Presenter .
Retrieve data from databases or other methods.
Responsible for any API calls and high-level data
manipulation such as
Registering a user with a database
Uploading data
Downloading data
Handling local storage
Calling an API
Models (not a must depending on the application)
Extensions of Entities with the addition of extra
members that might be platform-dependent. For
example, in the case of local databases, this can be
manifested as an isDelete d or an isDirty entry in
the local database. Such entries cannot be present in
the Entities as that would violate the Dependency
Rule since Domain should not be aware of the
implementation.
In the case of our application, models in
the Data layer will not be necessary as we do not
have a local database. Therefore, it is unlikely that we
will need extra entries in the Entities that are
platform-dependent.
Mappers
Map Entity objects to Models and vice-versa.
Static classes with static methods that receive either
an Entity or a Model and return the other.
Only necessary in the presence of Models
Extra
Utility classes if needed
Constants classes if needed

Device
Part of the outermost layer, Device communicates directly with the
platform i.e. Android and iOS. Device is responsible for Native
functionality such as GPS and other functionality present within the
platform itself like the filesystem. Device calls all Native APIs.

Contents of Data
Devices
Similar to Repositories in Data , Devices are classes that
communicate with a specific functionality in the platform.
Passed through the layers the same way Repositories are
pass across the boundaries of the layer: using polymorphism
between the App and Domain layer. That means
the Controller passes it to the Presenter then
the Presenter passes it polymorphically to the Usecase ,
which receives it as an abstract class.
Extra
Utility classes if needed
Constants classes if needed

Usage

Folder structure
lib/
app/ <--- application layer
pages/ <-- pages or screens
login/ <-- some page in the app
login_controller.dart <-- login controller extends `Controller`
login_presenter.dart <-- login presenter extends `Presenter`
login_view.dart <-- login view, 2 classes extend `View` and `ViewState`
resp.
widgets/ <-- custom widgets
utils/ <-- utility functions/classes/constants
navigator.dart <-- optional application navigator
data/ <--- data layer
repositories/ <-- repositories (retrieve data, heavy processing etc..)
data_auth_repo.dart <-- example repo: handles all authentication
helpers/ <-- any helpers e.g. http helper
constants.dart <-- constants such as API keys, routes, urls, etc..
device/ <--- device layer
repositories/ <--- repositories that communicate with the platform e.g. GPS
utils/ <--- any utility classes/functions
domain/ <--- domain layer (business and enterprise) PURE DART
entities/ <--- enterprise entities (core classes of the app)
user.dart <-- example entity
manager.dart <-- example entity
usecases/ <--- business processes e.g. Login, Logout, GetUser, etc..
login_usecase.dart <-- example usecase extends `UseCase` or
`CompletableUseCase`
repositories/ <--- abstract classes that define functionality for data and device
layers
main.dart <--- entry point

Example Code
Checkout a small example
https://github.com/pzverkov/flutter_clean_architecture

and a full application built


https://github.com/pzverkov/Flutter-CleanArchitecture-HnH .

View
import 'package:flutter_clean_architecture/flutter_clean_architecture.dart';
class CounterPage extends View {
@override
// Dependencies can be injected here
State<StatefulWidget> createState() => CounterState();
}

class CounterState extends ViewState<CounterPage, CounterController> {


CounterState() : super(CounterController());

@override
Widget buildPage() {
return MaterialApp(
title: 'Flutter Demo',
home: Scaffold(
key: globalKey, // using the built-in global key of the `View` for the scaffold or any other
// widget provides the controller with a way to access them via getContext(),
getState(), getStateKey()
body: Column(
children: <Widget>[
Center(
// show the number of times the button has been clicked
child: Text(controller.counter.toString()),
),
// you can refresh manually inside the controller
// using refreshUI()
MaterialButton(onPressed: controller.increment),
FlatButton(onPressed: () => controller.login, child: Text('Login')),

],
),
),
);
}
}

Widgets with Common Controller


In the event that multiple widgets need to use the
same Controller of a certain Page , the Controller can be
retrieved inside the children widgets of that page
via FlutterCleanArchitecture.getController<HomeController>
(context) .
For example:

import '../pages/home/home_controller.dart';
import 'package:flutter/material.dart';
import 'package:flutter_clean_architecture/flutter_clean_architecture.dart';

class HomePageButton extends StatelessWidget {


final String text;
HomePageButton({@required this.text});

@override
Widget build(BuildContext context) {
// use a common controller assuming HomePageButton is always a child of Home
HomeController controller =
FlutterCleanArchitecture.getController<HomeController>(context);
return GestureDetector(
onTap: controller.buttonPressed,
child: Container(
height: 50.0,
alignment: FractionalOffset.center,
decoration: BoxDecoration(
color: Color.fromRGBO(230, 38, 39, 1.0),
borderRadius: BorderRadius.circular(25.0),
),
child: Text(
text,
style: const TextStyle(
color: Colors.white,
fontSize: 20.0,
fontWeight: FontWeight.w300,
letterSpacing: 0.4),
),
),
);
}
}
Controller
import 'package:flutter_clean_architecture/flutter_clean_architecture.dart';

class CounterController extends Controller {


int counter;
final LoginPresenter presenter;
CounterController() : counter = 0, presenter = LoginPresenter(), super();

void increment() {
counter++;
}

/// Shows a snackbar


void showSnackBar() {
ScaffoldState scaffoldState = getState(); // get the state, in this case, the scaffold
scaffoldState.showSnackBar(SnackBar(content: Text('Hi')));
}

@override
void initListeners() {
// Initialize presenter listeners here
// These will be called upon success, failure, or data retrieval after usecase execution
presenter.loginOnComplete = () => print('Login Successful');
presenter.loginOnError = (e) => print(e);
presenter.loginOnNext = () => print("onNext");
}
void login() {
// pass appropriate credentials here
// assuming you have text fields to retrieve them and whatnot
presenter.login();
}
}

Presenter
import 'package:flutter_clean_architecture/flutter_clean_architecture.dart';

class LoginPresenter() {

Function loginOnComplete; // alternatively `void loginOnComplete();`


Function loginOnError;
Function loginOnNext; // not needed in the case of a login presenter

final LoginUseCase loginUseCase;


// dependency injection from controller
LoginPresenter(authenticationRepo): loginUseCase = LoginUseCase(authenticationRepo);

/// login function called by the controller


void login(String email, String password) {
loginUseCase.execute(_LoginUseCaseObserver(this), LoginUseCaseParams(email,
password));
}

/// Disposes of the [LoginUseCase] and unsubscribes


@override
void dispose() {
_loginUseCase.dispose();
}
}

/// The [Observer] used to observe the `Stream` of the [LoginUseCase]


class _LoginUseCaseObserver implements Observer<void> {

// The above presenter


// This is not optimal, but it is a workaround due to dart limitations. Dart does
// not support inner classes or anonymous classes.
final LoginPresenter loginPresenter;

_LoginUseCaseObserver(this.loginPresenter);

/// implement if the `Stream` emits a value


// in this case, unnecessary
void onNext(_) {}

/// Login is successful, trigger event in [LoginController]


void onComplete() {
// any cleaning or preparation goes here
assert(loginPresenter.loginOnComplete != null);
loginPresenter.loginOnComplete();

/// Login was unsuccessful, trigger event in [LoginController]


void onError(e) {
// any cleaning or preparation goes here
assert(loginPresenter.loginOnError != null);
loginPresenter.loginOnError(e);
}
}

UseCase
import 'package:flutter_clean_architecture/flutter_clean_architecture.dart';

// In this case, no parameters were needed. Hence, void. Otherwise, change to appropriate.
class LoginUseCase extends CompletableUseCase<LoginUseCaseParams> {
final AuthenticationRepository _authenticationRepository; // some dependency to be
injected
// the functionality is hidden behind this
// abstract class defined in the Domain module
// It should be implemented inside the Data or Device
// module and passed polymorphically.

LoginUseCase(this._authenticationRepository);

@override
// Since the parameter type is void, `_` ignores the parameter. Change according to the
type
// used in the template.
Future<Stream<void>> buildUseCaseStream(params) async {
final StreamController controller = StreamController();
try {
// assuming you pass credentials here
await _authenticationRepository.authenticate(email: params.email, password:
params.password);
logger.finest('LoginUseCase successful.');
// triggers onComplete
controller.close();
} catch (e) {
print(e);
logger.severe('LoginUseCase unsuccessful.');
// Trigger .onError
controller.addError(e);
}
return controller.stream;
}
}

class LoginUseCaseParams {
final String email;
final String password;
LoginUseCaseParams(this.email, this.password);
}
Background UseCase
A usecase can be made to run on a separate isolate using
the BackgroundUseCase class. Implementing this kind of usecase
is a little different than a regular usecase due to the constraints of an
isolate. In order to create a BackgroundUseCase , simply extend
the class and override the buildUseCaseTask method. This method
should return a UseCaseTask , which is just a function that has a
void return type and takes
a BackgroundUseCaseParameters parameter. This method should
be static and will contain all the code you wish to run on a separate
isolate. This method should communicate with the main isolate using
the por t provided in the BackgroundUseCaseParameters as
follows.

This example is of a BackgroundUseCase that performs matrix


multiplication.
class MatMulUseCase extends BackgroundUseCase<List<List<double>>,
MatMulUseCaseParams> {

// must be overridden
@override
buildUseCaseTask() {
return matmul; // returns the static method that contains the code to be run on an isolate
}

/// This method will be executed on a separate isolate. The [params] contain all the data
and the sendPort
/// needed
static void matmul(BackgroundUseCaseParams params) async {
MatMulUseCaseParams matMulParams = params.params as MatMulUseCaseParams;
List<List<double>> result = List<List<double>>.generate(
10, (i) => List<double>.generate(10, (j) => 0));

for (int i = 0; i < matMulParams.mat1.length; i++) {


for (int j = 0; j < matMulParams.mat1.length; j++) {
for (int k = 0; k < matMulParams.mat1.length; k++) {
result[i][j] += matMulParams.mat1[i][k] * matMulParams.mat2[k][j];
}
}
}
// send the result back to the main isolate
// this will be forwarded to the observer listneres
params.port.send(BackgroundUseCaseMessage(data: result));

}
}

Just like a regular [UseCase], a parameter class is recommended for


any [BackgroundUseCase]. An example corresponding to the above
example would be
class MatMulUseCaseParams {
List<List<double>> mat1;
List<List<double>> mat2;
MatMulUseCaseParams(this.mat1, this.mat2);
MatMulUseCaseParams.random() {
var size = 10;
mat1 = List<List<double>>.generate(size,
(i) => List<double>.generate(size, (j) => i.toDouble() * size + j));

mat2 = List<List<double>>.generate(size,
(i) => List<double>.generate(size, (j) => i.toDouble() * size + j));
}
}

Repository in Domain

abstract class AuthenticationRepository {


Future<void> register(
{@required String firstName,
@required String lastName,
@required String email,
@required String password});

/// Authenticates a user using his [username] and [password]


Future<void> authenticate(
{@required String email, @required String password});

/// Returns whether the [User] is authenticated.


Future<bool> isAuthenticated();

/// Returns the current authenticated [User].


Future<User> getCurrentUser();

/// Resets the password of a [User]


Future<void> forgotPassword(String email);

/// Logs out the [User]


Future<void> logout();
}

This repository should be implemented in Data layer:

class DataAuthenticationRepository extends AuthenticationRepository {


// singleton
static DataAuthenticationRepository _instance = DataAuthenticationRepository._internal();
DataAuthenticationRepository._internal();
factory DataAuthenticationRepository() => _instance;

@override
Future<void> register(
{@required String firstName,
@required String lastName,
@required String email,
@required String password}) {
// TODO: implement
}

/// Authenticates a user using his [username] and [password]


@override
Future<void> authenticate(
{@required String email, @required String password}) {
// TODO: implement
}

/// Returns whether the [User] is authenticated.


@override
Future<bool> isAuthenticated() {
// TODO: implement
}

/// Returns the current authenticated [User].


@override
Future<User> getCurrentUser() {
// TODO: implement
}

/// Resets the password of a [User]


@override
Future<void> forgotPassword(String email) {
// TODO: implement
}

/// Logs out the [User]


@override
Future<void> logout() {
// TODO: implement
}
}

If the repository is platform-related, implement it in the Device layer.

Entity
Defined in Domain layer.

class User {
final String name;
final String email;
final String uid;
User(this.name, this.email, this.uid);
}

Checkout a small example https://github.com/pzverkov/flutter_clean_architecture and a full


application built https://github.com/pzverkov/Flutter-CleanArchitecture-HnH .
Schematic

Use this as a reference to the Dependency Rule and NOT as a


mechanism to write the classes.

Visual representations
I think it’s always good to start with some visualization. Here are the most common pictures
of this concept.

First of all, it is important to understand that clean architecture is a


bundle of organizing principles. So therefore, everything is open to
personal adjustments as long as core ideas are kept intact. The
linked repository is a fork of the original project that brought this
architecture design idea to me. Feel free to check out the original
project as well, as it reflects further improvements.
It’s really about the Separation of Concerns
Dividing software into layers
Should be independent of frameworks
They should be testable
They should be independent of a UI
They should be independent of a database
They should be independent of interfaces to 3rd party
dependencies
The Clean Architecture Diagram
Innermost: “Enterprise / Critical Business Rules” – Entities
Next out: “Application business rules” – Use Cases
Next out: “Interface adapters” – Gateways, Controllers,
Presenters
Outer: “Frameworks” – Devices, UI, External Interfaces, DB
Moving inward, the level of abstraction and policy increase
The innermost circle is the most general/highest level
Inner circles are policies
Outer circles are mechanisms
Inner circles cannot depend on outer circles
Outer circles cannot influence inner circles.

Entities
Entities should be usable by many applications (critical business rules)
and should not be impacted by anything other than a change to the
critical business rule itself
They encapsulate the most general/high-level rules.

Use Cases
Use cases are application specific business rules
Changes should not impact the Entities
Changes should not be impacted by infrastructure such as a
database
The use cases orchestrate the flow of data in/out of the Entities and direct
the Entities to use their Critical Business Rules to achieve the use case

Interface Adapters
Converts data from data layers to use case or entity layers
Presenters, views and controllers all belong here
No code further in (use cases, entities) should have any knowledge of the
db.

Frameworks
These are the glue that hook the various layers up
The infrastructure details live Crossing Boundaries
Flow of control went from the controller, through the application use case,
then to the presenter
Source code dependencies point in towards the use cases
Dependency Inversion Principle
Use case needs to call a presenter – doing so would violate the
dependency rule – inner circles cannot call (or know about) outer
circles….
The use case would need to call an interface
The implementation of that interface would be provided by
the interface adapter layer – this is how the dependency is
inverted
This same type of inversion of control is used all
throughout the architecture to invert the flow of control

Data Crossing Boundaries


Typically, data crossing the boundaries consist of simple data structures
DO NOT PASS ENTITY OBJECTS OR DATA ROWS!
This would violate the dependency rules
Data is passed in the format that is most convenient to the inner circle /
layer
These are isolated, simple data structures
Meaning our DTOs needed to cross the boundaries should belong in
the inner circle, or at least their definition (interface, abstract class)

Testing
Creating a plugin architecture has the benefit of making your code
much more testable. It's really hard to test your code when there are
lots of dependencies. But when you have a plugin architecture, it’s
easy to just replace a database dependency (or whatever
component) with a mock object.
I've always had a terrible time testing the UI. I make a test that walks
through the GUI but as soon as I make a change to the UI the test
breaks. So I end up just deleting the test. I learned, though, that I
should create a Presenter object in the adapter layer. The Presenter
will take the output of the business rules and format everything as
the UI view needs it. Then the UI view object does nothing except
display the preformatted data that the Presenter provides. With this
setup you can test the Presenter code independently of the UI.
Create a special testing API to test the business rules. It should be
separate from the interface adapters so that the tests don't break
whenever the structure of the application changes.
Conclusion
The essence of the Clean Architecture book was that you need
to create a plugin architecture. Classes that might change at the
same time and for the same reason should be grouped together into
components. The business rule components are more stable and
should know nothing about the more volatile infrastructure
components, which deal with the UI, database, web, frameworks,
and other details. The boundary between component layers is
maintained by using interface adapters that translate the data
between the layers and keep the dependencies pointing in the
direction of the more stable inner components.
Conforming to the rules is not difficult but requires work and will
set you up to be able to plug and play pieces in the future. With
provided codebase and 3rd party library recommended in that
codebase it becomes so easy to implement Clean Architecture within
your Flutter project.
An important goal of clean architecture is to provide developers
with a way to organize code in such a way that it encapsulates
the business logic but keeps it separate from the delivery
mechanism.
Like Clean Code, Clean Architecture is filled with timeless
principles that can be applied no matter what language someone is
coding in.

You might also like