Sounds awesome? well, wait till we finish the app 😎!
CLEAN ARCHITECTURE!!… what is that mean? Ok, let us slowly explain some of the most important facts about it, shall we?
I assume you’ve already built apps whether using Flutter, Native, or anything else.. we usually don’t focus on how the data flow is working, writing independent data layers, separation of code, the real use of OOP, testing, features scaling, …etc.
All we want is finishing the app as soon as possible for shipping it out, BUT what if in some cases we’re building a big project that has multiple features, number of developers working on it, needs to be tested, and so on.. And if we imagine continuing building the app like we used to do or we just need to change something in the future… it’s definitely gonna be a messy place to work with!
The Clean Architecture is the most powerful solutions for building clean apps that multiple teams can work on, independent data layers, scalable for adding/removing features, testable, independent frameworks/tools, and can be easily maintained at any time.
Pros and Cons
- Boilerplate code
- May not be suitable for all kind of projects
- Can be implemented in multiple ways
- Independency of Frameworks/Tools
- Maintainable at any time
- Testability and Scalability
- Suitable for big teams/developers
- Well structured
What this series will explain?
Well, luckily we are gonna be explaining and building a simple News App that gets its data from REST API and also caches the data locally in the devices. It’s a lot of work, patience and focusing on how we will implement each part of the app in a very pretty clean way.. and the final results will be awesome.
I’ve separated the entire article into 3 parts, and what this part will explain is what tools/libraries we are gonna be using, the folder structure, different data sources, setup the project, and the main architecture.
The source code will be available at the end of each part of this series as a separated branch on my GitHub.
- retrofit: is a source code generator package that uses Dio as an http client to generate the proper methods we need to deal with REST APIs based on abstraction, it’s inspired by the Android Retrofit.
- floor: is a typesafe, reactive, lightweight source code generator package that uses the sqlite to store its data locally, it’s again inspired by Android Room.
- flutter_bloc: a flutter state management (You can still use any kind of state management you prefer to work with such as provider).
- equatable: a flutter package that makes comparing dart objects by equality is much easier.
- get_it: a service locator (also dependency injector).
- flutter_hooks: hooks inspired by React Hooks.
- lint: a flutter package that helps you write clean code which follows the Dart’s Style Guidelines, (explained more later).
Another note I would point here is the dependency injection, as you saw we’ll use the get_it package, we could also use the source code generator package called injectable (inspired by Android Dagger) that uses annotations to automatically inject dependencies, but this will make the project a little bit harder to explain all those tools and libraries at once so we will only use the get_it and inject the dependencies manually and I’ll explain how to use this injectable in the future articles.
- ionicons: a set of beautiful icons available as a flutter package.
REST API (remote data source)
This article will use this News API for retrieving data as json. You have to sign in to get your own api key.
Take a look at the diagram below and consider creating folders as shown here
The (data, domain, presentation) folders will be explained later in details but right now we have to know what the (core, config) are for?
The config folder includes the configuration about the app (themes, routes, …etc) nothing more. The core folder is much more important, it includes the core stuff we need across the app (globally) so if we have a job/action that will be required by all (features/layers) then it’s better to be in the core. It also includes some other folder such as (resources, utiles, …etc).
The injector.dart file inside the src folder will be responsible for injecting our dependencies using the get_it package.
The Architecture and Dependency Rule
If you have created the folders as organized in the diagram, then great job.. because now we will talk more about the architecture that we will use in details and the purpose of each layer.
Firstly, let us take a deep look at this diagram below:
Take a look again and below is an explanation for each layer.
The bottom layer as shown in the above diagram, its responsibility is to deal directly with the raw data from different data sources (REST API, GraphQl, Sqlite, …etc). The raw data then will be mapped or converted into models (dart objects) using some serialization methods to serialize/deserialize data (Json, Xml, …etc) from and into.
The models in the data layer are different from the entities in the Domain layer, and the reason why we have this method is because in some cases if we want to change the raw data serialization from (Json) to (xml), this changes will not affect the internal entities (Domain’s Entities). The models in the data layers will extends the properties from the Domain’s entities and it will always depend on the Domain layer since the models are extending all of the properties from the entities.
The data layer is also contains the real implementations of the abstraction in the domain layer including the repositories, so we define the interface (abstracted class) in the domain layer.. then we implement that class in the data layer and this is useful because we have the ability to change or add multiple implementations without interacting with the Domain layer.
Keep in mind that repositories returns entities and not models, because the contract written inside the Domain layer, and with that.. we can say that the Data layer depends on Domain layer.
This layer contains two important parts: The Blocs (also ViewModels) and The UI
What’s BloC and Why?
What’s happening here is that any user interaction that requires data from outside this layers (Remote or Local), the blocs will handle that kind of interactions. Consider the blocs like viewmodels but instead of having one single class, the pattern of the Bloc is to separate those interactions (like user inputs) as Events, the bloc will process that event and return/emit results back to the UI as States. The UI part then can listen to the states (stream of states) and do actions, build widgets or anything upon those states.
The reason why most of developers like using bloc is because of the code separation (event, state, bloc) and this is also plays good role of having clean code.
Now, the presentation layer as we now know its parts.. also depends on the Domain layer since the bloc will use the injected domain dependencies (such as usecases) to process its jobs.
The most interesting layer in the architecture, this layer contains only the internal entities and what this mean is that our domain’s entities are completely independent from any changes that could occur outside this layer. Both Presentation and Data layers depending on this layer, since the data layer will implement what ever contracts written here.. and the presentation layer will use those contracts with the implementations to be used as an injected dependencies.
Keep in mind that the presentation layer will only gets data as entities and not models, this is why we separate each layer individually and independently.
Big projects may even use a kind of mapping objects in each layer so they make sure that each layer will not interact or depend on other, but now we will not do that since we don’t need that much engineering.
We forgot something, didn’t we? yes, and here I would point that we did saw the use cases in the diagram as part of the domain layer, but we didn’t read anything about it…!!
A use cases are individual classes that depends on those repositories we defined earlier, and normally a use case should only perform one precise action (getting articles, post data, signing in, …etc). One use case may takes multiple repositories injected to it, and the bloc (viewmodel) may also takes multiple use cases injected to it.
We should think about our application as a group of use cases that describe the intent of the applications and group of plugins that give those use cases access to outside world. (Uncle Bob)
As we said before, lint is a dev dependency package that helps us write the right and clean piece of code that follows specific rules.
After adding the package to the pubspec.yaml file, create file in the root directory and call it analysis_options.yaml then specify the rules you want to apply and consider following them.
Useful VS Code Extensions
- bloc: this extension helps you create blocs with all basic codes and files (events, state).
- dart data class generator: a useful extension if you want to quickly generate JSON serialization methods (toJson, fromJson).
- dart import: a very handy extension that makes sorting and organizing dart imports much easier and cleaner.
Firstly, I assume you’ve already configured the (themes, routes) as you’d prefer. Now create a file inside the core/utils and call it constants.dart which will only contains the const values.
And with all of that set.. it’s time to get ready to the real work. Let’s clean up the main.dart file from any Flutter auto generated code and make it looks like this
Note that in dart, naming a constant should always starts with k to indicate that this is a constant variable, like we did here kMaterialAppTitle
Great, now everything should be just fine.. next we will talk in details about how we will get our data (Breaking News) remotely from the REST API.
Part 2 will explain and implement the remote data (REST API).
Consider giving the repo a pretty small star ❤️ for a little support… and see you next buddy.. 😎