We already saw enough of the game rules with CLEAN in the previous volumes of A Cleaner Flutter, now we have to go and write some code.
So go get your developer glasses and let's get our hands a little dirty.
⚠️ All the code that we're going to write in this article is pure Dart, with no dependencies on the Flutter SDK.
⚠️ I'm going to alternate between these two terms quite a bit in the next section so you don't get confused:
Abstract classes are the same as interfaces. In Dart, there is no keyword for creating an interface, but the abstract class does exactly that.
Before explaining entities, I notify you that with this article we begin to explain the domain layer of our project, which consists of the contracts that we're going to set up in our project.
We're not going to sign anything, it's an expression. I'll explain what contracts are.
We call the abstract classes or interfaces (if you come from other languages) as contracts because they settle rules: properties, actions ... that must be fulfilled for a specific class.
In this example, we can see an abstract class / interface that defines a network manager. This class is called a contract because any class that extends from it has to implement its methods and has access to its properties. As we can see in the following image:
Here we see that since
NetworkManager doesn't implement
isConnected, it throws an error. Then,
NetworkManager is forced to sign a contract to implement
Got it? Great. And now, why do we make contracts?
As we talked about in the previous articles, we need our code to be scalable and that means it's easy to extend. That way, having abstract classes we can make the implementations that we want of the interfaces taking into consideration different types of technologies or in the case of Flutter, they can be different packages with a separate implementation for each one.
And in addition to all this, contracts make testing our app much easier, since we can create test classes with packages like mockito and thus run our tests in classes that have the methods established in our contracts.
Now that we make that clear, let's go with the entities.
When you are going to start a project from scratch, which part do you code first?
In my case, I code the classes of the data that I'm going to use throughout my entire application. I consider this to be a good practice as it helps us lay down the information that will flow through all of our logic.
The entities can be ordinary classes that represent abstractions of actors in real life, or something very abstract such as classes of a framework like Flutter with
We are not going to complicate it much.
The entities must be our data types, or classes that are used in different parts of our software.
At least that's what Uncle Bob says, and it seems right to me. Although everything ends up being an entity even in the outermost layer, we define in our version of Clean that our entities are the objects that can be returned to us - or we can send to - an API, taking the most famous example.
When we look at the models in the data layer you probably understand the usefulness of first abstracting an entity that simply defines properties and some general behaviors and then extending its functionality into models that implement more specific methods.
Let's say that an entity's purpose is to define the essential characteristics of an object. The specific features are implemented by the model, because the models are in charge of being used for specific use cases.
That's a lot specific.
The code would be like this:
Response states the properties that every API response must have. These are simple responses to simplify the example, I know that not all answers in an API will return what this class has. Let me finish.
Now that we have our contract, we can create a model that "signs" it, basically I mean implement or extend it. I say to sign because by inheriting from that class we are agreeing to implement all the methods it has and comply with the properties it sets.
For example with
ResponseModel, we create a
String in the same constructor and simply pass that property to the class we are inheriting from, to fulfill that property.
In this other example we do exactly the same as in the previous one, with the only difference that we add a property that is no longer the basis of our
Response entity and thus we fulfill one of the characteristics that we talked about in the previous volumes: we extend the existing functionality without altering it in the process.
A valid extra point to mention is that as this is the basis of our entire graph, when we make a change at the entity level we will have errors in our models and for the same reason, in any other part that these models are used.
There we see the rule of dependency in action and how it helps us to have control of the changes in our code by the parts that depend on others.
Perhaps I have written long and hard on something very simple but I'm interested in understanding the purpose of this separation. We could easily make our models all at once with the features we consider necessary, but the code would become a bit repetitive and even messy.
I hope you have assimilated well the concepts and especially the separation into layers that we are doing to extend the functionality and order of our code.
This was the simplest part and the basis for the rest (out of the concepts), in the next volume we will continue exploring the domain layer with the repositories, I promise you that it will be a little more entertaining.
You can share this article to help another developer to continue improving their productivity when writing applications with Flutter.
Also if you liked this content, you can find even more and keep in contact with me on my socials: