So far in this series, we’ve covered some beginner’s mistakes and gone through the clean architecture. In this last part, we will cover the last piece of the puzzle: labels, or more precisely, components.
First, I’ll remove the stuff we don’t use on Android projects and I’ll add some stuff that we do use but that isn’t found in the original Uncle Bob’s diagram. It looks like this:
I’ll go from the most abstract center to the edges.
Entities, aka domain objects or business objects, are the core of the app. They represent the main functionality of the app, and you should be able to tell what the app is about just by looking at them. They contain business logic, but it’s constrained to them only—validation and stuff like that. They don’t interact with the gritty outside-world details, and they also don’t handle persistence. If you had a news app, the entities would be Category, Article, and Commercial, for example.
Use cases, aka interactors, aka business services, are an extension of the entities, an extension of the business logic, that is. They contain the logic that isn’t constrained only to one entity but handles more of them. An indicator of a good use case is that you can describe what it does in a simple sentence using a common language—for example, “Transfer money from one account to another.” You can even use such nomenclature to name the class, e.g., TransferMoneyUseCase.
Repositories serve to persist the entities. It’s as simple as that. They are defined as interfaces and serve as output ports for the use cases that want to perform CRUD operations on entities. Additionally, they can expose some more complex operations related to persistence such as filtering, aggregating, and so on. Concrete persistence strategies, e.g., database or the Internet, are implemented in the outer layers. You could name the interface AccountRepository, for instance.
Presenters do what you would expect them to do if you are familiar with the MVP pattern. They handle user interactions, invoke appropriate business logic, and send the data to the UI for rendering. There is usually some mapping between various types of models here. Some would use controllers here, which is fine. The presenter we use is officially called the supervising controller. We usually define one or two presenters per screen, depending on the screen orientation, and our presenters’ lifecycles are tied to that of a view. One piece of advice: try to name your methods on a presenter to be technology agnostic. Pretend that you don’t know what technology the view is implemented in. So, if you have methods named onSubmitOrderButtonClicked and onUserListItemSelected in the view, the corresponding presenter methods that handle those events could be named submitOrder and selectUser.
This component has already been teased before in the notifications (again) example. It contains the implementations of the gritty Android stuff such as sensors, alarms, notifications, players, all kinds of *Managers, and so on. It is a two-part component. The first part is the interfaces defined in the inner circles that business logic uses as the output port for communication with the outer world. The second part, and that’s drawn in the diagram, are implementations of those interfaces. So, you can define, for example, interfaces named Gyroscope, Alarm, Notifications, and Player. Notice that the names are abstract and technology agnostic. Business logic doesn’t care how the notification will be shown, how the player will play the sound, or where the gyroscope data comes from. You can make an implementation that writes the notifications to the terminal, write the sound data to the log, or gather the gyroscope data from the predefined file. Such implementations are useful for debugging or creating a deterministic environment for you to program in. But you will, of course, have to make implementations such as AndroidAlarm, NativePlayer, etc. In most cases, those implementations will just be wrappers around Android’s manager classes.
DB & API
No philosophy here. Put the implementations of repositories in this component. All the under-the-hood persistence stuff should be here: DAOs, ORM stuff, Retrofit (or something else) stuff, JSON parsing, etc. You can also implement a caching strategy here or simply use in-memory persistence until you are done with the rest of the app. We had an interesting discussion in the team recently. The question was this: Should the repository expose methods such as fetchUsersOffline (fetchUsersFromCache) and fetchUsersOnline (fetchUsersFromInternet)? In other words, should business logic know where the data comes from? Having read everything from this post, the answer is simple: no. But there is a catch. If the decision about the data source is part of the business logic—if the user can choose it, for example, or if you have an app with explicit offline mode—then you CAN add such a distinction. But I wouldn’t define two methods for every fetch. I would maybe expose methods such as enterOfflineMode and exitOfflineMode on the repository. Or if it applies to all the repositories, we could define an OfflineMode interface with enter and exit methods and use it on the business logic side, leaving repositories to query it for the mode and decide it internally.
Even less philosophy here. Put everything related to the Android UI here. Activities, fragments, views, adapters, etc. Done.
The following diagram shows how have we divided all these components into Android Studio modules. You might find another division more appropriate.
We group entities, use cases, repositories, and device interfaces into the domain module. If you want an extra challenge with a reward of eternal glory and a totally clean design, you can make that module a pure Java module. It will prevent you from taking shortcuts and putting something related to the Android here.
The device module should have everything related to Android that’s not data persistence and UI. The data module should hold everything related to data persistence, as we’ve already said. You cannot make those two into Java modules because they need access to various Android stuff. You can make them into Android library.
Finally, we group everything related to the UI (including presenters) into the UI module. You can explicitly name it UI but because of all the Android stuff here, we leave it named “app,” just as Android Studio named it during the creation of the project.
Is it better?
To answer that question, I’ll ditch Uncle Bob’s diagram and sprawl the components described before into a diagram like those we have used to rate previous types of architectures. After doing that, we get this:
Now let’s apply the same criteria that we have used on previous architectures.
It’s all neatly separated by module level, package level, and class level. So SRP should be satisfied.
We have pushed Android and the real-world stuff as far out on the outskirts as we can. Business logic doesn’t touch the Android directly anymore.
We have nicely separated classes that are easy to test. Classes touching the world can be tested using Android test cases; the one not touching it can be tested using JUnit. Someone malevolent would maybe call that class explosion. I call it testable. :)
Clean architecture: It may be complicated – but it’s worth it
I hope that my carefully chosen criteria, which was not made up just to favor the clean architecture, will convince you to give it a try. It seems complicated, and there are lots of details here, but it’s worth it. Once you wire it all up, testing is much easier, bugs are easier to isolate, new features are easy to add, the code is more readable and maintainable, everything works perfectly, the universe is satisfied.
So, this is it. If you still haven’t done so, look at the previous articles in the series: Mistakes and Clean Architecture. If you have any comments or questions, leave feedback below. We are always interested in hearing what you think.
Read the 4th part of our Android Architecture Series.
You can also check out other parts: