Android Architecture About This Ebook We are sharing with you the process behind the most of our fantastic Android apps! After a series of technical blog posts, we put together this exclusive White Paper. Learn about using Android Architecture for building amazing apps in this 47 page E-book, covering everything from the basics, introducing and applying Clean Architecture on Android, to testing. About Five Five is a mobile design and development agency founded in Croatia with strong presence in New York. We help brands like Rosetta Stone, Rhapsody, Marriott, and many others, to build and execute their mobile strategy and products. Five and our product company, We employ over 130 designers and developers in Croatia and the US. Table of contents Part 1: Every new beginning is hard Pain points of the early Android years: does Google care at all? Four golden rules of Android architecture (or any architecture really) I. Satisfy a multitude of stakeholders. II. Encourage separation of concerns. III. Run away from the real world (Android, DB, Internet…). IV. Enable your components to be testable. First iteration – God activity Second iteration – MVP Third iteration – MVP + managers Conclusion 2 3 4 5 6 6 6 7 8 9 10 Part 2: The clean architecture Master of your domain I. Dependency rule II. Abstraction III. Communication between layers Conclusion 11 12 12 13 15 18 Part 3: Applying clean architecture on Android Entities Use cases Repositories Presenters Device DB & API UI Modules Is it better? It may be complicated – but it’s worth it 19 20 20 20 21 21 22 22 22 23 25 Part 4: Applying Clean Architecture on Android, Hands on (source code included) Foundations I. Domain II. Data III. Device IV. The easiest part, app module (UI module) Dependencies Bricks, a lot of bricks Domain UI Let’s throw some rx in the mix Data and Device Models, models everywhere. Conclusion 26 26 27 27 27 27 27 29 29 32 34 35 36 36 Part 5: How to Test Clean Architecture I. Testing clean architecture II. Naming tests III. Testing domain IV. Testing the data V. Testing the app module Conclusion 37 38 38 40 43 44 47 PART 1 Every new beginning is hard Tomislav Homan The goal of this post series is to give an overview of our struggles with the architecture PART 1: EVERY NEW BEGINNING IS HARD of Android apps. I realized that however painful the implementation of android app architecture might be, it turned out to be the fundament of every fantastic app I have been working on. Every technology has its natural evolution. Or to be more precise, its community undergoes the process of evolution. Early adopters of a new computer language or a framework are enthusiasts who just want to get their hands on the technology and get some stuff done as quickly as possible. Typically, the new community is small and has limited potential for knowledge transfer among developers, that is, everybody is learning from their own mistakes since there are no architecture guidelines available yet. Android in its early days wasn’t an exception. It was cool and attractive for developers, and it offered an opportunity for the early adopters to be a part of community around 2 this fast-developing technology. Pain points of the early Android years: does Google care at all? You can argue that there are numerous senior guys out there who have a lot of experience with different technologies and who will come up with standards in no time. Well, not necessarily. If there is a strong company behind the technology that counts on making money, sure, they’ll have their own technology evangelists who will spread the word about how this new language is cool and you can do a lot of stuff with it and it’s easy peasy to learn, scale, and satisfy millions of customers. Microsoft does that often with its technologies. On the other hand, when Google bought Android I honestly think they treated it just as some other side project. If you have been in the Android world since its beginnings, you must remember the frustrations and the feelings that Google simply doesn’t care about you. Those couple of guys who have extra experience, ability, and the will to help the community are now Android superstars or little demi-gods—Jake Wharton for example. “When Google bought Android, I honestly think they treated it just as some other side project.” Another thing you could say is that you don’t have to think a lot about the architecture and the organization of your code because the framework does that for you. Android forces you to put your screen stuff into activities, reusable screen stuff into fragments, and background stuff into services and to talk to others using broadcast receivers, so it pretty much makes your life good. Right? No. First of all, there are some good practices and principles that are simply good, regardless PART 1: EVERY NEW BEGINNING IS HARD of technology. The single responsibility principle for example, or the dependency inversion principle, program against interfaces, kill global state, try to murder all states, etc. Frameworks rarely force you to follow principles. Au contraire, they violate them in the worst possible ways. Think of context, the God object that you have to use everywhere, all kinds of singleton managers, fragments with their lifecycles (what kind of nightmarish life is that), AsyncTasks that are usually implemented incorrectly, so they suck the memory, food, water, and air out of your app. It’s easy for a young developer without guidance to make a monster instead of an app. Think about it as a spaghetti monster black hole—it’s good as a pastafarian deity, but not as good an app as it could be. 3 And lastly, technology and frameworks hide the purpose of the app. Ok, it’s an Android app, but what kind of an Android app? Newsreader? Music player? Language-learning app? Weather app? Maybe it is a yet another to-do list app. If everything is bundled up in the classes provided by the framework, you cannot tell. As Robert Martin, aka Uncle Bob, says, “Your architecture should scream the purpose of the app.” More technically said, business logic should be clearly separated and independent of the framework. Four golden rules of Android architecture (or any architecture really) PART 1: EVERY NEW BEGINNING IS HARD 4 I hope that it’s clear now that you cannot rely on the framework to make your code neat and organized, especially with Android. At Five, we realized that a long time ago but lacked the experience to come up with a bull’s-eye right away. It takes a lot of time for fails to manifest, and you cannot change the whole architecture in the middle of the project. You also can’t get time to refactor the old project into the new, cool, “wanna be” best architecture. So, we took the gradual approach, slowly building up our architecture from project to project and learning from our mistakes. There are couple of goals that we think our architecture should satisfy, and we can compare our approaches against those goals. Good architecture should do the following: 1. Satisfy a multitude of stakeholders. 2. Encourage separation of concerns. 3. Run away from the real world (Android, DB, Internet…). 4. Enable your components to be testable. I. Satisfy a multitude of stakeholders. Stakeholder (in this post) is any person interested in the development of your app. Besides developers, there are UI designers, UX designers, PMs, DB admins, QA, etc. And of course, you can’t organize your code in a way that a UX designer, for example, could open a project and understand everything immediately and maybe even make some changes. It’s a unicorn. PART 1: EVERY NEW BEGINNING IS HARD 5 What I mean is that you CAN organize your code in a way that a developer who currently works WITH the UX designer can manage only the stuff related to the UX. So, all interactions are separated in the classes/modules/components/whatever whose job is interactions, and that particular developer works only on those components while working on the UX part of the app. II. Encourage separation of concerns. What I have said before is an example of separation of concerns. We encourage that particular approach because it maps nicely to the organization of teams and project phases, but your architecture should also encourage separation of concerns generally. Separation of concerns, or the single responsibility principle, says that every component should have only one reason to change. III. Run away from the real world (Android, DB, Internet…). This particular point, to run away from the real world, was already mentioned before. We have already said that we want to scream what the app really does; that’s it. We want to emphasize business logic and leave framework details under the hood. This point should be even stronger: we would like to not only hide framework details but all the details related to the outside world. PART 1: EVERY NEW BEGINNING IS HARD All the nitty gritty dirty Android stuff like sensors, notification mechanisms, screen details, database access, Internet access, etc. IV. Enable your components to be testable. You should unit test your app as much as possible and your architecture should allow 6 you to do it. If you can’t unit test everything, you should at least cover your business logic with tests. Separation from the real world goes nicely with this. It’s easier to test your business logic if it is clearly separated from the rest of the app. First iteration – God activity public final class UsersActivity extends ListActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //... new ListUsers().execute(); } private final class ListUsers extends AsyncTask<Void, Void, Void> { @Override protected Void doInBackground(Void... voids) { // final SQLiteOpenHelper sqLiteOpenHelper = ... // JsonObjectRequest jsObjRequest = new JsonObjectRequest // (Request.Method.GET, url, null, new Response.Listener<JSONObject>() { // MySingleton.getInstance(this).addToRequestQueue(jsObjRequest); // showData(user); return null; } } } You have probably seen code like this back in the “olden times.” If you haven’t, you’re young. What’s wrong with this? Everything! PART 1: EVERY NEW BEGINNING IS HARD 7 We have an activity that touches databases, goes to the Internet, does parsing, spawns threads, and renders data. So, all the stakeholders are looking at the single class, none of the concerns is separated, it’s not testable, and business logic is mixed with the Android stuff. Second iteration – MVP The first approach obviously didn’t work. One of the first things we tried was MVP, or model-view-presenter. Everybody is familiar with MVP. It is one of the most popular architectural patterns. It looks something like this: Here we have separated views that are actually our Android fragments, we have (domain) models that represent our business, and finally we have presenters that coordinate all of that. It’s better for sure. Concerns are somewhat separated, stakeholders aren’t so confused anymore, and you can write some tests. We are still mixed up with the real world, though, as the presenter directly touches the database and everything. The elephant in the room here is the presenter. It’s a god object. It deals with the models, it sends the data to the view, it holds the business logic (business logic are those gears :) ), and it goes to the database and Internet, fetches sensor data, etc. So yeah, better, but PART 1: EVERY NEW BEGINNING IS HARD 8 it could get way more better. Third iteration – MVP + managers What does government do when it doesn’t know what to do? It forms an agency. What do developers do when they don’t know what to do? They introduce some manager. You don’t have to name it “manager.” There are lots of names for these kind of classes: utils, helpers, fooBarBuzz-ator, etc. So, we introduced managers. To be honest, this even sort of worked. Business logic is contained in the manager classes. Stakeholders know where to look at, concerns are sort of separated but they could be even more so, you can write more tests but you are still touching Android directly so you have to write Android test cases and prefill the database to test business logic and that’s slow. And yeah, managers tend to be huge beasts and very quickly become hard to maintain. You can argue that it doesn’t need to get more complicated than it is, that you can deliver code faster by using simpler architecture, but there will be more bugs with that approach, and maintainability will suffer. PART 1: EVERY NEW BEGINNING IS HARD 9 Conclusion In this first part of the series, we went through the challenges of creating an Android architecture that actually works. Good Android architecture should satisfy a multitude of stakeholders, encourage separation of concerns, emphasize business logic and leave framework details under the hood and enable all your components to be testable. In the second part of the series, we will show you how we managed that what worked for us. Until then, do you have any suggestions on how to create a proper Android workflow? What were the problems you encountered? PART 1: EVERY NEW BEGINNING IS HARD 10 PART 2 The clean architecture Tomislav Homan In the first part of the series, we covered the mistakes we had made on our path to finding the architecture that works. In this part, we will present the so-called Clean Architecture. The first image you come across when you google “clean architecture” is this: PART 2: THE CLEAN ARCHITECTURE 11 It is also known as onion architecture, because the diagram looks like an onion (and it makes you cry when you realize how much boilerplate you have to write); or ports and adapters, because there are, as you can see, some ports in the bottom right corner. Hexagonal architecture is yet another similar architecture. Clean architecture is the brainchild of previously-mentioned Uncle Bob, who also wrote books on Clean Code and Clean Coder. The main point of this approach is that the business logic, also known as domain, is at the center of the universe. Master of your domain When you open your project, you should already know what this app is all about, regardless of the technology. Everything else is an implementation detail. For example, persistence—it’s a detail. Define an interface, make in-memory a quick and dirty implementation, and don’t think about it until the business is done. Then you can decide how you really want to persist the data. Database, internet, combination, file system—maybe leave them in-memory, maybe it turns out you don’t have to persist them at all. In a sentence: inner layers contain business logic, outer layers contain implementation details. That being said, there are a couple of features of clean architecture that enable that: 1. Dependency rule 2. Abstraction 3. Communication between layers I. Dependency rule Dependency rule can be explained by the following diagram: PART 2: THE CLEAN ARCHITECTURE 12 Outer layers should depend on inner layers. Those three arrows in the red square represent dependencies. Instead of “depends on,” maybe it’s better to use terms like “sees,” “knows about,” or “is aware of.” In these terms, outer layers see, know about, and are aware of inner layers, but inner layers neither see nor know about, nor are aware of, outer layers. As we said previously, inner layers contain business logic and outer layers contain implementation details. Combined with the dependency rule, it follows that business logic neither sees, nor knows, nor is aware of, implementation details. And that’s exactly what we are trying to accomplish. How you implement the dependency rule is up to you. You can put it in different packages, but be careful not to use “outer” packages from “inner” packages. However, if somebody is not aware of the dependency principle, nothing would stop them from breaking it. A better approach would be to separate the layers into different Android modules, for example, and adjust dependencies in the build file so that the inner layer simply cannot use the outer layer. At Five, we use something in between. One more thing worth mentioning is that, although nobody can stop you from skipping layers—for example, using some red layer component from the blue layer component—I strongly encourage you to access only components from the layer next to yours. II. Abstraction Abstraction principle has already been hinted at before. It says that, as you are moving towards the middle of the diagram, stuff becomes more abstract. That makes sense: as we said that the inner circle contains business logic and the outer circle contains implementation details. PART 2: THE CLEAN ARCHITECTURE 13 You can even have the same logical component divided between multiple layers, as shown in the diagram. The more abstract part can be defined in the inner layer, and the more concrete part in the outer layer. An example will make it clear. We can define an abstract interface as “Notifications” and put it in the inner layer, that way your business logic can use it to show the notification to the user when it wants to. On the other hand, we can implement that interface in such a way that the implementation uses Android notification manager to show the notifications, and then put that implementation in the outer layer. In this way, business logic can use a feature—notifications in our example—but it doesn’t know anything about implementation details: how the actual notifications are implemented. Moreover, business logic doesn’t even know that implementation details exist. Look at the following image: PART 2: THE CLEAN ARCHITECTURE 14 When you combine abstraction with the dependency rule, it turns out that our abstract business logic using notifications neither sees nor knows about, nor is aware of, concrete implementation that uses the Android notification manager. That is good, because we can switch that concrete implementation and business logic won’t even notice it. Let’s just briefly compare this to how abstraction and dependencies look and work when using standard three-tier architecture. You can see in the diagram that all dependencies in the standard three-tier architecture go to the database. That means that abstraction and dependency don’t match. Logically, the business layer should be the center of the app, but it isn’t, as dependencies go towards the database. The business layer knows about the database and it shouldn’t. It should be the other way around. Whereas in clean architecture, dependencies go to the business (inner) layer and abstraction also rises towards the business layer, so they match nicely. This is important because abstraction is the theory and dependencies are the practice. Abstraction is the logical layout of the app, and dependencies are how it is actually composed together. In clean architecture these two match up, while in the standard three-tier architecture they don’t; and this can quickly lead to all kinds of logical inconsistencies and mess if you aren’t careful. PART 2: THE CLEAN ARCHITECTURE III. Communication between layers Now that we have divided our app into the modules, nicely separated everything, put business logic in the center of our app and implementation details in the outskirts, everything looks great. But you’ve probably quickly run into an interesting problem. If your UI is an implementation detail, the internet is an implementation detail, and business logic is in between, how the heck can we fetch the data from the internet, pass it through the business logic and then send it to the screen? Business logic is in the middle and should mediate between the internet and the UI, but it doesn’t even know that those two guys exist. This is a question of communication and 15 data flow. We would like the data to be able to flow from the outer layers to the inner ones and vice versa, but the dependency rule doesn’t allow that. Let’s strip that down to the simplest example. We have only two layers, the green one and the red one. The green one is outer and knows about the red one, and the red one is inner and knows only about itself. We want the data to flow from the green one to the red one and back to the green one. The solution has already been hinted at before and is shown in the following diagram: The part of the diagram at the bottom right side shows the data flow. Data goes from the controller, through the use case (or replace use case with the component of your choice) input port, then through the use case itself, and after that through the use case PART 2: THE CLEAN ARCHITECTURE output port back to the presenter. Arrows on the main part of the diagram denote composition and inheritance—composition indicated by a filled arrowhead, inheritance an empty arrowhead. Composition is also known as has-a relationship, and inheritance is-a relationship. “I” and “O” in the circles represent input and output ports. It can be seen that a controller defined in the green layer has an input port defined in the red layer. Use case (gears, business logic, whatever—not important now) is (or implements) that input port and has an output port. And finally, the presenter defined in the green layer is actually an output port defined in the red layer. 16 We can match that to the dataflow now. The controller has an input port—it literally has a reference to it. It calls a method on it, so that data goes from the controller to the input port. But the input port is an interface, and the actual implementation is the use case: so it has called a method on a use case and the data flows to the use case. Use case does something and wants to send the data back. It has a reference to the output port—as the output port is defined in the same layer—so it can call the method on it. Hence, data goes to the output port. And finally, the presenter is, or implements, the output port; that’s the magic part. As it implements the output port, the data actually flows into it. The trick is that the use case knows only its output port; the world ends at this output port. It’s up to the presenter to implement it. It could’ve been implemented by anything, as use case doesn’t know or care, and is aware only of its little world inside its layer. We can see that, by combining composition and inheritance, we can make the data flow in both directions, although inner layers aren’t aware that they are communicating with the outside world. Take a quick glance at the following diagram: PART 2: THE CLEAN ARCHITECTURE You can see that both has-a and is-a arrows point to the middle—the same as dependency arrows. Well, it’s logical. According to the dependency rule, it’s the only possible way. The outer layer can see the inner layer, but not the other way around. The only tricky part is that is-a relationship, although it points to the middle, reverses the dataflow. 17 Notice that it’s the inner layer’s responsibility to define its input and output ports, so that the outer layers can use them to establish communication with it. I’ve said that this solution has already been hinted at before, and it has. The notifications example that illustrated abstraction is also an example of this kind of communication. We have a notifications interface defined in the inner layer that business logic can use to show notifications to the user, but we also have an implementation defined in the outer layer. In that case, the notifications interface is business logic’s output port, which it uses to communicate to the outer world—to the concrete implementation in this example. You don’t have to name your classes FooOutputPort or BarInputPort; we name the ports just to explain the theory. Conclusion So, is this overly complicated, overly obscured over-engineering? Well, it’s simple when you get used to it. And it’s necessary. It allows us to make that nice abstraction / dependency match actually communicate and work in the real world. Maybe all of this reminds you of the string theory: beautiful, theoretically elegant, but overly complicated and we still don’t know whether it works, but in our case – it does. :) So that’s it for the second part of the series. The last, third part, after all that we have learned about the theory and architecture, will cover all you need to know about labels on those diagrams, or in other words – separate components. We will show you a real life clean architecture applied on Android. PART 2: THE CLEAN ARCHITECTURE 18 PART 3 PART 3: APPLYING CLEAN ARCHITECTURE ON ANDROID 19 Applying clean architecture on Android Tomislav Homan 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 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 PART 3: APPLYING CLEAN ARCHITECTURE ON ANDROID Category, Article, and Commercial, for example. Use cases 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 handle more of them. An indicator of a good use case is that you can describe what it does in a simple sentence using 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 Repositories serve to persist the entities. It’s as simple as that. They are defined as in- 20 terfaces and serve as output ports for the use cases that want to perform CRUD opera- tions 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 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. Device This component has already been teased before in the notifications (again) example. It PART 3: APPLYING CLEAN ARCHITECTURE ON ANDROID 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. 21 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. UI Even less philosophy here. Put everything related to the Android UI here. Activities, fragments, views, adapters, etc. Done. PART 3: APPLYING CLEAN ARCHITECTURE ON ANDROID 22 Modules 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: PART 3: APPLYING CLEAN ARCHITECTURE ON ANDROID 23 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. PART 3: APPLYING CLEAN ARCHITECTURE ON ANDROID 24 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. :) PART 3: APPLYING CLEAN ARCHITECTURE ON ANDROID 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, 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 25 below. We are always interested in hearing what you think. PART 4 PART 4: APPLYING CLEAN ARCHITECTURE ON ANDROID, HANDS ON ON Applying Clean Architecture on Android, Hands on (source code included) Mihael Franceković In the last part of our Android Architecture series, we adjusted Clean Architecture a bit to the Android platform. We separated Android and the real world from our business logic, satisfied stakeholders and made everything easily testable. The theory is great, but where do we start when we create a new Android project? Let’s get our hands dirty with clean code and turn that blank canvas into an architecture. Foundations We will lay down the foundations first – create modules and establish dependencies 26 among them to be on par with the dependency rule. Those will be our modules, in order from the most abstract one to the concrete implementations: 1. Domain Entities, use cases, repositories interfaces, and device interfaces go into the domain module. Ideally, entities and business logic should be platform agnostic. To feel safe, and to prevent us from placing some android stuff in here, we will make it a pure java module 2. Data The data module should hold everything related to data persistence and manipulation. Here we will find DAOs, ORMs, SharedPreferences, network related stuff like Retrofit services and similar. 3. Device The device module should have everything related to Android that’s not data persistence and UI. In example, wrapper classes for ConnectivityManager, NotificationManager and misc sensors. PART 4: APPLYING CLEAN ARCHITECTURE ON ANDROID, HANDS ON ON We will make both Data and Device modules android modules, as they must know about Android and cannot be pure java. 4. The easiest part, app module (UI module) This module is already created for you by the Android studio when you create a project. Here you can place all the classes related to the Android UI such as presenters, controllers, view models, adapters and views. Dependencies Dependency rule defines that concrete modules depend on the more abstract ones. You might remember from the third part of this series that UI (app), DB – API (data) and Device (device) stuff is together in the outer ring. Meaning that they are on the same abstraction level. How do we connect them together then? Ideally, these modules would depend only on the domain module. In that case, dependencies would look somewhat like a star: 27 But, we are dealing with Android here and things just cannot be perfect. Because we need to create our object graph and initialize things, modules sometimes depend on an another module other than the domain. For example, we are creating object graph for dependency injection in the app module. PART 4: APPLYING CLEAN ARCHITECTURE ON ANDROID, HANDS ON ON 28 That forces app module to know about all of the other modules. Our adjusted dependencies graph: Bricks, a lot of bricks Finally, it’s time to write some code. To make things easier, we will take an RSS Reader app as an example. Our users should be able to manage their RSS feed subscriptions, fetch articles from a feed and read them. Domain let’s start with the domain layer and create our core business models and logic. Our business models are pretty straightforward: – Feed – holds RSS feed related data like the url, thumbnail URL, title and description – Article – holds article related data like the article title, url and publication date And for our logic, we will use UseCases, aka. Interactors. They encapsulate small parts of business logic in concise classes. All of them will implement general UseCase contract: public interface UseCase<P> { PART 4: APPLYING CLEAN ARCHITECTURE ON ANDROID, HANDS ON ON 29 interface Callback { void onSuccess(); void onError(Throwable throwable); } void execute(P parameter, Callback callback); } The very first thing our users will do when they open our app is add a new RSS feed subscription. So to start with our interactors, we will create AddNewFeedUseCase and its helpers to handle feed addition and validation logic. AddNewFeedUseCase will use FeedValidator to check feed URL validity, and we will also create the FeedRepository contract which will provide our business logic some basic CRUD capabilities to manage feed data: public interface FeedRepository { int createNewFeed(String feedUrl); List<Feed> getUserFeeds(); List<Article> getFeedArticles(int feedId); boolean deleteFeed(int feedId); } Note how our naming in the domain layer clearly propagates the idea of what our app is doing. Put everything together, our AddNewFeedUseCase looks like this: public final class AddNewFeedUseCase implements UseCase<String> { private final FeedValidator feedValidator; PART 4: APPLYING CLEAN ARCHITECTURE ON ANDROID, HANDS ON ON private final FeedRepository feedRepository; @Override public void execute(final String feedUrl, final Callback callback) { if (feedValidator.isValid(feedUrl)) { onValidFeedUrl(feedUrl, callback); } else { callback.onError(new InvalidFeedUrlException()); } } private void onValidFeedUrl(final String feedUrl, final Callback callback) { try { feedRepository.createNewFeed(feedUrl); callback.onSuccess(); } catch (final Throwable throwable) { callback.onError(throwable); } } } *Constructors are omitted for the sake of brevity. 30 Now, you might be wondering, why is our use case, as well as our callback, an interface? To demonstrate our next problem better, let’s investigate GetFeedArticlesUseCase. It takes a feedId -> fetches feed articles via FeedRespository -> returns feed articles Here comes the data flow problem, the use case is in between presentation and data layer. How do we establish communication between layers? Remember those input and output ports? PART 4: APPLYING CLEAN ARCHITECTURE ON ANDROID, HANDS ON ON Our use case must implement the input port (interface). Presenter calls the method on the use case, and the data flows to the use case (feedId). Use case maps feedId to feed articles and wants to send them back to the presentation layer. It has a reference to the output port (Callback), as the output port is defined in the same layer, so it calls a method on it. Hence, data goes to the output port – presenter. We will tweak our UseCase contracts a bit: public interface UseCase<P, R> { interface Callback<R> { interface Callback { void onSuccess(R return); void onSuccess(); void onError(Throwable throwable); } 31 public interface CompletableUseCase<P> { void onError(Throwable throwable); } } void execute(P parameter, Callback<R> callback); void execute(P parameter, Callback callback); } UseCase interfaces are Input ports, and Callback interfaces are output ports. GetFeedArticlesUseCase implementation is as follows: class GetFeedArticlesUseCase implements UseCase<Integer, List<Article>> { private final FeedRepository feedRepository; @Override public void execute(final Integer feedId, final Callback<List<Article>> callback) { try { callback.onSuccess(feedRepository.getFeedArticles(feedId)); } catch (final Throwable throwable) { callback.onError(throwable); } } } One last thing to note in the domain layer is that Interactors should only contain business logic. And in doing so, they can use repositories, combine other interactors and use some utility objects like FeedValidator in our example. PART 4: APPLYING CLEAN ARCHITECTURE ON ANDROID, HANDS ON ON UI Awesome, we can fetch articles, let’s show them to the user now. Our view has a simple contract: interface View { void showArticles(List<ArticleViewModel> feedArticles); void showErrorMessage(); void showLoadingIndicator(); } Presenter for that view has a very simple presentation logic. It fetches articles, maps them to the view models and passes on to the view, simple, right? 32 Simple presenters are yet another feat of the clean architecture and presentation-business logic separation. Here is our FeedArticlesPresenter: class FeedArticlesPresenter implements UseCase.Callback<List<Article>> { private final GetFeedArticlesUseCase getFeedArticlesUseCase; private final ViewModeMapper viewModelMapper; public void fetchFeedItems(final int feedId) { getFeedArticlesUseCase.execute(feedId, this); } @Override public void onSuccess(final List<Article> articles) { getView().showArticles(viewModelMapper.mapArticlesToViewModels(articles)); } @Override public void onError(final Throwable throwable) { getView().showErrorMessage(); } } PART 4: APPLYING CLEAN ARCHITECTURE ON ANDROID, HANDS ON ON 33 Note that FeedArticlesPresenter implements Callback interface and it passes itself to the use case, it is actually an output port for the use case and in that way it closes the data flow. This is the concrete example of the data flow that we mentioned earlier, we can tweak labels on the flow diagram to match this example: Where our parameter P is integer feedId, and return type R is a list of the Articles. You do not have to use presenters to handle presentation logic, we could say that Clean architecture is “frontend” agnostic – meaning you can use MVP, MVC, MVVM or anything else on top of it. Let’s throw some rx in the mix Now, if you were wondering why there is such hype about RxJava, we will take a look at the reactive implementation of our use cases: public interface UseCase<P, R> { Single<R> Completable execute(P parameter); (P parameter); } } PART 4: APPLYING CLEAN ARCHITECTURE ON ANDROID, HANDS ON ON 34 public interface CompletableUseCase<P> { Callback interfaces are now gone and we use rxJava Single/Completable interface as our output port. Reactive implementation of the GetFeedArticlesUseCase: class GetFeedArticlesUseCase implements UseCase<Integer, List<Article>> { private final FeedRepository feedRepository; @Override public Single<List<Article>> execute(final Integer feedId) { return feedRepository.getFeedArticles(feedId); } } And reactive FeedArticlePresenter is as follows: class FeedArticlesPresenter { private final GetFeedArticlesUseCase getFeedArticlesUseCase; private final ViewModeMapper viewModelMapper; public void fetchFeedItems(final int feedId) { getFeedItemsUseCase.execute(feedId) .map(feedViewModeMapper::mapFeedItemsToViewModels) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(this::onSuccess, this::onError); } private void onSuccess(final List articleViewModels) { getView().showArticles(articleViewModels); } private void onError(final Throwable throwable) { getView().showErrorMessage(); } } PART 4: APPLYING CLEAN ARCHITECTURE ON ANDROID, HANDS ON ON Although it’s a bit hidden, the same data flow inversion principle still holds, because without RxJava presenters were implementing the callback, and with RxJava subscribers are also contained in the outer layer – somewhere in the presenter. Data and Device Data and Device modules contain all of the implementation details that business logic does not care about. It only cares about the contracts, allowing you to easily test it and swap out implementations without touching the business logic. Here you can use your favorite ORMs or DAOs to store data locally and network services to fetch data from the network. We will implement FeedService to fetch articles, and use FeedDao to store articles data on the device. Each data source, both network, and local storage, will have its own models to work with. In our example, they are ApiFeed – ApiArticle and DbFeed – DbArticle. 35 Concrete implementation of the FeedRepository is found in the Data module as well. Device module will hold implementation of the Notifications contract that is a wrapper around the NotificationManager class. We could perhaps use Notifications from our business logic to show the user a notification when there are new articles published in which the user might be interested in and drive engagement. Models, models everywhere. You might have noticed that we mentioned more models than just entities or business models. In reality, we also have db models, API models, view models and of course, business models. It is a good practice for every layer to have its own model that it works with, so your concrete details, such as views, do not depend on the specific details of your lower layer implementations. This way, you won’t have to break unrelated code if you, for example, decide to change from one ORM to another. To make that possible, it is necessary to use object mappers in each layer. In the examPART 4: APPLYING CLEAN ARCHITECTURE ON ANDROID, HANDS ON ON 36 ple, we used ViewModelMapper to map domain Article model to the ArticleViewModel. Conclusion Following these guidelines, we created a robust and versatile architecture. At first, it may seem like a lot of code, and it kind of is, but remember that we are building our architecture for the future changes and features. And if you do it correctly, future you will be thankful. In the next part, we will cover maybe the most important part of this architecture, its testability and how to test it out. So, in the meantime, what part of the architecture implementation did you find interesting the most? PART 5 How to Test Clean Architecture David Geček PART 5: HOW TO TEST CLEAN ARCHITECTURE Why should you care about testing? Programmers, like any human, make mistakes. We may forget about that edge case that we implemented last month, or about how some method behaves when we pass it an empty string. There is no point in taking an app after every change and trying every possible click, tap, gesture and orientation change just to be sure that everything works. And you’ll probably forget about that triple tap in the right top corner while rotating the device so everything will crash when the user does it and something throws null pointer exception. Users do silly stuff, and we need to make sure that every class does what it’s supposed to and that every part of the app handles everything we throw at it. That’s why we write automated tests. 37 1. Testing clean architecture Clean architecture is all about maintainability and testability. Every part of the architecture has exactly one purpose. We just need to specify it and check that it actually does its job every time. Now, let’s be realistic. What can we actually test? Everything. Genuinely, if you structure your code right, you can test everything. It’s up to you what to test. Unfortunately, there is usually no time to test everything. Testability. That’s the first step. The second step is testing the right way. Let’s remind us about the old rule of FIRST: Fast – Tests should be really fast. Part of the second fast. There is no point in writing tests if it takes minutes or hours to execute them. No one will check tests if that’s the case! Isolated – Test one unit of the app at the time. Arrange everything on that unit to behave exactly how you want and then poke the testing unit. Assert that it behaves correctly. Repeatable – Test should have the same results every time it is executed. It should not depend on some nondeterministic data. Self-validating – Framework should know if test has passed or not. There should not be any manual checking of tests. Just check if everything is green and that’s it :) Timely – Tests should be written about the same time as the code, or even before the code! So, we made a testable app, and we know how to test. What about tests’ names? PART 5: HOW TO TEST CLEAN ARCHITECTURE 2. Naming tests Is it important how we name the tests, honestly? Well, it has more indirect than direct value. It reflects your attitude towards the tests, your way of thinking what to test. Let’s meet our victim: public final class DeleteFeedUseCase implements CompletableUseCaseWithParameter { @Override public Completable execute(final Integer feedId) { //implementation } } 38 First, naive way would be to write tests like this: @Test public void executeWhenDatabaseReturnsTrue() throws Exception { } @Test public void executeWithErrorInDatabase() throws Exception { } This is called implementation-style naming. It is tightly coupled with class implementation. When we change the implementation, we need to change what we expect from the class. These are usually written after the code, and the only good thing about them is that they are written quickly. The second way is example-style naming: @Test public void doSomethingWithIdsSmallerThanZero() throws Exception { } PART 5: HOW TO TEST CLEAN ARCHITECTURE 39 @Test public void ignoreWhenNullIsPassed() throws Exception { } Example-style tests are examples of system usage. They are nice when testing edge cases, but don’t use them for everything, they are too coupled with the implementation. Now, let’s try to abstract our view of this class and move away from the implementation. What about this: @Test public void shouldDeleteExistingFeed() throws Exception { } @Test public void shouldIgnoreDeletingNonExistingFeed() throws Exception { } We know exactly what we expect from this class. This test class can be used as class’ specification, hence the name – specification-style naming. Name does not say anything about the implementation, and from the tests’ names – specification – we can write the actual concrete class. Specification-style names are usually the best way to go, but if you think you cannot test some implementation specific edge cases, you can always throw in few example-style tests. Theory ends here and we are ready to get our hands dirty! 3. Testing domain Let’s see how can we test use cases. Structure of use cases in our Reedly app looks like this: PART 5: HOW TO TEST CLEAN ARCHITECTURE Problem is that EnableBackgroundFeedUpdatesUseCase is final and if it’s mock is needed for some other use case testing, it cannot be done. Mockito doesn’t allow mocking of final classes. Use cases are referenced by its implementation, so let’s add another layer of interfaces: 40 Now we can mock EnableBackgroundFeedUpdatesUseCase interface. But in our everyday practice we concluded that this is very confusing while developing, middle layer interfaces are empty and use cases don’t actually need to have interfaces. Use cases do only one job and it says so right in the name – “enable background feed updates use case”, there is nothing to abstract! OK, let’s try this – we don’t need to make use cases final. We try to make everything that we can final, it makes more structured and more optimized code. We can live with use cases not being final, but there must be a better way. We came to the solution to use mockito-inline. It makes the unmockable, mockable. With new versions of the Mockito, it is possible to enable mocking of the final classes. PART 5: HOW TO TEST CLEAN ARCHITECTURE Here is example of use case implementation: public final class EnableBackgroundFeedUpdatesUseCase implements CompletableUseCase { private final SetShouldUpdateFeedsInBackgroundUseCase setShouldUpdateFeedsInBackgroundUseCase; private final FeedsUpdateScheduler feedsUpdateScheduler; //constructor @Override public Completable execute() { return setShouldUpdateFeedsInBackgroundUseCase.execute(true) .concatWith(Completable.fromAction(feedsUpdateScheduler::scheduleBackgroundFeedUpdates)); } 41 } When testing use cases we should test that use case calls correct methods in repositories or executes other use cases. We should also test that use case returns proper callback: private EnableBackgroundFeedUpdatesUseCase enableBackgroundFeedUpdatesUseCase; private SetShouldUpdateFeedsInBackgroundUseCase setShouldUpdateFeedsInBackgroundUseCase; private FeedsUpdateScheduler feedUpdateScheduler; private TestSubscriber testSubscriber; @Before public void setUp() throws Exception { setShouldUpdateFeedsInBackgroundUseCase = Mockito.mock(SetShouldUpdateFeedsInBackgroundUseCase.class); feedUpdateScheduler = Mockito.mock(FeedsUpdateScheduler.class); testSubscriber = new TestSubscriber(); enableBackgroundFeedUpdatesUseCase = new EnableBackgroundFeedUpdatesUseCase(setShouldUpdateFeedsInBackgroundUseCase, feedUpdateScheduler); } @Test public void shouldEnableBackgroundFeedUpdates() throws Exception { Mockito.when(setShouldUpdateFeedsInBackgroundUseCase.execute(true)).thenReturn(Completable.complete()); enableBackgroundFeedUpdatesUseCase.execute().subscribe(testSubscriber); Mockito.verify(setShouldUpdateFeedsInBackgroundUseCase, Mockito.times(1)).execute(true); PART 5: HOW TO TEST CLEAN ARCHITECTURE 42 Mockito.verifyNoMoreInteractions(setShouldUpdateFeedsInBackgroundUseCase); Mockito.verify(feedUpdateScheduler, Mockito.times(1)).scheduleBackgroundFeedUpdates(); Mockito.verifyNoMoreInteractions(feedUpdateScheduler); testSubscriber.assertCompleted(); } Here TestSubscriber from Rx is used, so proper callback can be tested. It can assert completion, emitted values, number of values etc. 4. Testing the data Here is very simple repository method, it just uses one DAO method: public final class FeedRepositoryImpl implements FeedRepository { private final FeedDao feedDao; private final Scheduler backgroundScheduler; //constructor @Override public Single feedExists(final String feedUrl) { return Single.defer(() -> feedDao.doesFeedExist(feedUrl)) .subscribeOn(backgroundScheduler); } //more methods } When testing repositories, you should arrange DAOs – make them return or receive some dummy data, and check that repository is handling the data in a proper way: private FeedService feedService; private FeedDao feedDao; private PreferenceUtils preferenceUtils; private Scheduler scheduler; PART 5: HOW TO TEST CLEAN ARCHITECTURE private FeedRepositoryImpl feedRepositoryImpl; @Before public void setUp() throws Exception { feedService = Mockito.mock(FeedService.class); feedDao = Mockito.mock(FeedDao.class); preferenceUtils = Mockito.mock(PreferenceUtils.class); scheduler = Schedulers.immediate(); feedRepositoryImpl = new FeedRepositoryImpl(feedService, feedDao, preferenceUtils, scheduler); } @Test public void shouldReturnInfoAboutFeedExistingIfFeedExists() throws Exception { Mockito.when(feedDao.doesFeedExist(DataTestData.TEST_COMPLEX_URL_STRING_1)).thenReturn(Single.just(true)); 43 final TestSubscriber testSubscriber = new TestSubscriber<>(); feedRepositoryImpl.feedExists(DataTestData.TEST_COMPLEX_URL_STRING_1).subscribe(testSubscriber); Mockito.verify(feedDao, Mockito.times(1)).doesFeedExist(DataTestData.TEST_COMPLEX_URL_STRING_1); Mockito.verifyNoMoreInteractions(feedDao); testSubscriber.assertCompleted(); testSubscriber.assertValue(true); } When testing mappers (converters), specify input to the mapper, and exact output you expect from the mapper, then assert they are equal. Do the same for services, parsers etc. 5. Testing the app module On top of the clean architecture, we like to use MVP. Presenters are just ordinary Java objects, not connected with Android, so there is nothing special about testing them. Let’s see what can we test: public final class ArticlesPresenterTest { @Test public void shouldFetchArticlesAndPassThemToView() throws Exception { } @Test public void shouldFetchFavouriteArticlesAndPassThemToView() throws Exception { } PART 5: HOW TO TEST CLEAN ARCHITECTURE @Test public void shouldShowArticleDetails() throws Exception { } @Test public void shouldMarkArticleAsRead() throws Exception { } @Test public void shouldMakeArticleFavourite() throws Exception { } @Test public void shouldMakeArticleNotFavorite() throws Exception { } } 44 Presenters usually have a lot of dependencies. We inject dependencies into presenters via @Inject annotation, not via the constructor. So in tests below, we need to use @Mock and @Spy annotations: public final class ArticlesPresenter extends BasePresenter implements ArticlesContract.Presenter { @Inject GetArticlesUseCase getArticlesUseCase; @Inject FeedViewModeMapper feedViewModeMapper; // (...) more fields public ArticlesPresenter(final ArticlesContract.View view) { super(view); } @Override public void fetchArticles(final int feedId) { viewActionQueue.subscribeTo(getArticlesUseCase.execute(feedId) .map(feedViewModeMapper::mapArticlesToViewModels) .map(this::toViewAction), Throwable::printStackTrace); } // (...) more methods } PART 5: HOW TO TEST CLEAN ARCHITECTURE 45 @Mock just makes plain mock out of the class. @Spy let’s you use the existing instance with all of the methods working, but you can mock some methods and “spy” which methods are called. Mocks are injected into the presenter via @InjectMocks annotation: @Mock GetArticlesUseCase getArticlesUseCase; @Mock FeedViewModeMapper feedViewModeMapper; @Mock ConnectivityReceiver connectivityReceiver; @Mock ViewActionQueueProvider viewActionQueueProvider; @Spy Scheduler mainThreadScheduler = Schedulers.immediate(); @Spy MockViewActionQueue mockViewActionHandler; @InjectMocks ArticlesPresenter articlesPresenter; Then some setting up is required. View is mocked manually because it is injected via constructor and we call presenter.start() and presenter.activate() so presenter is prepared and started: @Before public void setUp() throws Exception { view = Mockito.mock(ArticlesContract.View.class); PART 5: HOW TO TEST CLEAN ARCHITECTURE 46 articlesPresenter = new ArticlesPresenter(view); MockitoAnnotations.initMocks(this); Mockito.when(connectivityReceiver.getConnectivityStatus()).thenReturn(Observable.just(true)); Mockito.when(viewActionQueueProvider.queueFor(Mockito.any())).thenReturn(new MockViewActionQueue ()); articlesPresenter.start(); articlesPresenter.activate(); } When everything is ready, we can start writing the tests. Prepare everything and make sure that the presenter calls the view if needed: @Test public void shouldFetchArticlesAndPassThemToView() throws Exception { final int feedId = AppTestData.TEST_FEED_ID; final List<article> articles = new ArrayList<>(); final Article = new Article (AppTestData.TEST_ARTICLE_ID, feedId, AppTestData.TEST_ STRING, AppTestData.TEST_LINK, AppTestData.TEST_LONG_DATE, false, false); articles.add(article); final List<ArticleViewModel articleViewModels = new ArrayList <>(); final ArticleViewModel articleViweModel = new ArticleViewModel(AppTestData.TEST_ARTICLE_ ID, AppTestData.TEST_STRING, AppTestData.TEST_LINK, AppTestDAta.TEST_STRING, false, false); articleViewModels.add(articleViewModel); Mockito.when(getArticlesUseCase.execute(feedID)).thenReturn(Single.just(articles)); Mockito.when(feedViewModeMapper.mapArticlesToViewModels(Mockito.anyList())).thenReturn(articleViewModels); articlesPresenter.fetchArticles(feedId); Mockito.verify(getArticlesUseCase, Mockito.times(1)).execute(feedId); Moclito.verify(view, Mockito.times(1)).showArticles(articleViewModels); } PART 5: HOW TO TEST CLEAN ARCHITECTURE 47 Conclusion Think about testing before and during coding. That way you can write testable and decoupled code. Use your tests as class specification, and if possible write them before the code. Don’t let your ego get in the way, we all make mistakes. Therefore, we need to have a process to defend the application from ourselves! Be sure to check our other Android Architecture post below: Android Architecture Part 4: Applying Clean Architecture on Android,