How hard is it to clean the clean arch?
I’ve seen a lot of Flutter developers adopting Flutterando’s Clean Architecture Proposal. Well… this doesn’t surprise me, I’m one of them.
After working on several projects with clean arch within various teams, I noticed that adopting some not-spoken practices can enhance the development process.
Clean arch is a bunch of concepts presented in the Clean Code Book Series written by Robert C. (Uncle Bob) Martin. All hail the man!!
At least the first book of this series (Clean Code) is a "must-read" book for all developers.
Flutterando's Proposal makes use of clean code concepts to generate scalable programming code on Flutter/Dart environment. Separating the system into 4 layers: Presenter, Domain, Infra, and External.
It's not my point to explain the architecture. So, if more knowledge about it is needed, click here.
One controller per widget
On the presenter layer, we have widgets and controllers. Controllers are our widget state management class. And you want this separation to not gather interface render and interface logic.
Independent of the state management tool that you use, controllers should contain only information about the widget's state management.
The practice to highlight here is to avoid having more than one controller per widget.
For example, in the initial stages of a project, usually you need a user registration form and a user editing form that have the same fields and rules.
The difference between these forms is the initial value of the field and the endpoint invoked. There is plenty of devs that would create a single controller with a flag for each situation here.
It sure makes the job done, but it's not enough for a professional. These forms can/tend to change their interfaces asymmetrically. Making us in a near future inflate our code, disrespecting the Single Responsibility Principle.
Do you want me to use Ctrl+C Ctrl+V on my code? Well… yes! Avoid duplication is important, but also are SRP, readability, and maintainability. You need to balance the trade-offs.
Watch out! There are exceptions, for instance, if you're creating a category of widgets that must follow the same logic such as the AnimationController and the built-in implicit animation widgets.
Controllers only see what the widget needs
Continuing our talk about controllers, the clear arch splits the concepts of widget logic (Controllers) and business logic (Usecases).
That means that controllers should only have access to information that is pertinent to their widget.
For example, on a specific change password form there are just 2 fields. password and confirm the password. But our endpoint also needs the ID of the current user.
As this ID is not visible on the form, the controller should not know it. The getUserId logic has to be in the Usecase or in the Repository. But be sure of what you're doing.
Force error handling only on Usecases
But on professional apps, a simple login operation can be executed with several steps. Such as:
- Save login event on firebase analytics
- Invoke login endpoint
- Save the username locally to autofill the login form next time.
- Save the authentication token locally to keep the user logged
This is our business logic so should be contained in the Usecase class. Failures can happen here. That means usecases can use the "throw" keyword.
If the Usecases dependencies, i.e. repositories and services, also return Either/Result values the error handling gonna mess with the readability, and the functions bigger than it needs.
Drivers are dangerous! Start with them.
Intuitively we should start developing a new feature on the domain layer. But worked better for me to keep the Fail Fast Principle in mind.
On developing a new feature… Where it is most likely to show the infeasibility of the new content? The answer is: where you don't have control.
You have less control over the Presenter and External layers.
The Presenter is limited by the capacities of the Flutter framework, but with the great Google developers team and quick updates, it's quite reliable now, especially for mobile applications. Usually, we won't find anything that makes the project unfeasible here.
The External Layer is the communication with all libs and APIs our project needs. Most of its content is created by someone unknown and without supervision.
A lib that you want to add to your project can have out-of-date documentation and dependencies that conflict with your project.
Before starting a new feature, guarantee you have a functional driver. If something gonna be wrong that should happen on your first step.
Although the impressive contribution of Flutterando's Proposal for Flutter development doesn't mean we cannot improve even more.
We are the ones who know the necessities of our project. It's our responsibility to search for concepts, principles, and practices that deliver more value to our clients and teammates.
I just show some of them that worked for me. You shouldn't consider these as rules but as suggestions. Tell me if they make sense to you.