Rosie – Let’s Dive Into Clean Architecture
Stop wasting your time, start using Clean Architecture
Recently clean architecture on Android became very popular topic. I believe everyone who has ever tried to make maintainable and testable application realizes that this is very hard without clear separation of every layer. Thanks to Uncle Bob’s Clean Architecture and it’s dependency rule (source code dependencies can only point inwards) we can make applications that are not only testable, but also fully independent on UI, database and framework.
Let’s imagine, you can leave your business logic and change whatever you want (platform or source for your data), without rebuilding whole project. Sounds great! So, sooner or later (and probably sooner) you’ll need to implement this few simple rules in your application. But it’s not always so obvious how to do it, especially for beginners. And here comes Rosie!
Rosie – the only woman you need
Who is this Rosie? And why is she here a superhero? Let me explain.
Rosie is an Android framework to help you create application that follows principles of Clean Architecture. It separates layers from each other and creates communication between them. So here we have:
View
Here comes all UI – activities, fragments, views and adapters for them. No business logic is here, only things that are necessary for showing app to the user. Rosie offers RosieActivity and RosieFragment for this layer.
Presenter
It’s the intermediate layer between UI and business logic. This layer is known from Model-View-Presenter pattern and it communicates directly View with Domain layer. It provides data to be shown on view with RosiePresenter.
Domain
Domain is the proper layer for business logic. Here we should put all use cases and business rules. It’s place for list of possible scenarios (e.g. getAllProducts(), or addPersonToList()). It’s important to not put Android dependencies in our code from this moment. All imports in Domain and Repository layer (and ideally in Presenter, but it’s not always possible) should come from pure Java. Rosie uses command patterns to execute use cases and have special RosieUseCase classes to help with creating use cases.
Repository
Repository is layer to encapsulate entities, data and business objects. We can implement different Repositories for API, Cache, Database, Cloud, or any other data source. The main rule is that Domain layer should not know from which source the data is taken – it shouldn’t matter. Rosie offers RosieRepository to handle few data sources, e.g. ReadableDataSource, WriteableDataSource, CacheDataSource.
Using Rosie
For each layer Rosie provides their own classes. For example, we create RosieActivity with our UI, which is supported by RosiePresenter. When our RosiePresenter wants to retrieves data, he creates call to one of the RosieUseCase. RosieUseCase make request to RosieRepository to get some data and RosieRepository decides if take data from cache (CacheDataSource) or e.g. from API (ReadableDataSource).
That’s the simplest case of using Rosie. It’s great way to organize whole application, even if we haven’t done anything with Clean Architecture. We can also implement pagination (by using PaginatedDataSource), we can use Dagger, ButterKnife and Renderers (last one is helpful when we want to use pagination). We can make global error handling (e.g. we can catch all errors and show them on a view).
Rosie – is this the real life? Is this just fantasy?
After all this great things I’ve read about Rosie I decided to try it. I created a small app using The Star Wars API (swapi.co). I made everything very similar to application from Rosie’s sample. Implementation was quite easy, but during this I found small issues that bother me:
- Unfortunately, Rosie uses Dagger 1, instead of Dagger 2 for Dependency Injection. It’s worse for performance (making graph at runtime, using reflection) and for code readability (it’s hard to read generated code). Fortunately, I saw that this is one of the issues on GitHub, so there’s a chance to change this.
- RosieActivity, which we should use for our activities in project, extends from FragmentActivity. This class is good when we want to use Fragments and Loaders. But when we want to use support library action bar features, we should use AppCompatActivity. So why Rosie can’t use this class instead of FragmentActivity? It’ll be easier for everyone.
While making app, I’ve tried also to create some tests, because that’s the main reason I’ve decided to try Rosie. Unfortunately, also here I noticed a few things that made inconvenience (code not possible to execute is annotated)
- While testing presenter you can’t mock your view in easy way. I wanted to call some methods from presenter and verify with Mockito if proper methods on view were called, unfortunately setter for view is package-private and bindings presenter with activity is made with reflection. Hard to test.
1234567891011121314151617181920public class MainPresenterTest {@Mock UseCaseHandler useCaseHandler;@Mock GetPeople getPeople;private MainPresenter mainPresenter;@Testpublic void testUpdate_emptyList() throws Exception {//givenPaginatedCollection<Person> emptyList = PaginatedCollectionUtils.createPaginatedCollection(new ArrayList<>());when(getPeople.getAllPeopleInCache()).thenReturn(emptyList);//whenmainPresenter.update();//thenverify(getPeople).getAllPeopleInCache();verify(mainPresenterView).showPeople(emptyList.getItems());verify(mainPresenterView).showHasMore(emptyList.hasMore());} - Testing use cases is problematic since methods annotated with @UseCase use reflection. If we want to mock listener to onSuccess or onError, we can’t do it with Mockito.
1234567891011121314public class GetPeopleTest {private GetPeople getPeople;@Testpublic void testGetPeople() throws Exception {//whengetPeople.getPeople(Page.withOffsetAndLimit(0, 0));//thenverify(listener).onSuccess();}} - Testing presenter in full isolation of UseCaseHandler is impossible due to RosiePresenter’s method createUseCaseCall() (it’s method for calling our use case annotated with @RosieUseCase).
- It’s a big impediment that there are no tests for Presenter/Domain/Repository layer for sample app on Rosie’s GitHub.
Conclusion
After using Rosie for a while I can say that I’ll give it one more try and implement this framework in one of my applications. But is Rosie a must-have for every application? No. Rosie is great for beginners to dive into clean architecture and to see how each layer should be separated, but for bigger application I think it’s better to implement own version (maybe based on Rosie) to fit their needs. Even so, I recommend everyone to try on their own with Rosie.
About the author
Ready to take your business to the next level with a digital product?
We'll be with you every step of the way, from idea to launch and beyond!
Hi Droids On Roids!
I’m Pedro, one of the authors of Rosie. First of all, thanks for the blog post!
After reading your feedback I’ve decided to give you an answer for all the issues you have found using Rosie 🙂
1. The usage of Dagger 1 instead of Dagger 2. We decided to use Dagger 1 instead of Dagger 2 when we started coding because Dagger 2 was not released and is in our roadmap to migrate to Dagger 2. Here is the issue: https://github.com/Karumi/Rosie/issues/8
2. RosieActivity using FragmentActivity instead of AppCompatActivity. We have already added to the project a class named RosieAppCompatActivity you can use in your demo application. Here is the new class: https://github.com/Karumi/Rosie/blob/master/rosie/src/main/java/com/karumi/rosie/view/RosieAppCompatActivity.java We will release a new version with this class this week.
3. Adding unit tests to the Presenter. Something you should know is that, we not always write unit tests for the presenters we code. If you review the sample application we use instrumentation tests written using Espresso and the scope of the test goes from the view implementation, the Activity, to the repository. However, we have decided to change the presenter lifecycle and the setView methods visibility to public in order to improve the class testability fron the unit test point of view. Now you can write unit tests for your presenters. This change will be published in the next release :). Once you start writing tests for your presenters remeber to change the TaskScheduler used to create the UseCaseHandler to execute your use cases synchronoulsy, this approach is similar to the one followed by the RxJava guys and the TestScheduler you can use for your tests.
4. To be able to create an easy to use and read use cases we decided to use reflection to invoke the use case methods. This was the only approach we found to be able to implement a Command Pattern with different execute method and callback signatures. This means that if you are using a library like mockito to create your test doubles, the UseCaseHandler will not find any method annotated and therefore the use case will not be executed. This does not mean that the use cases are not testable but that you have to write your own test doubles for your use cases, wich is quite common in other languages like Swift.
The last issue you pointed “It’s a big impediment that there are no tests for Presenter/Domain/Repository layer for sample app on Rosie’s GitHub.” is not true. Most of the code in the sample project is covered with tests. You can review all the tests we wrote for the sample here: https://github.com/Karumi/Rosie/tree/master/sample/src/androidTest/java/com/karumi/rosie/sample. All these tests are based on the Android Instrumentation Test Framework and run in the Android emulator. If you review the test scope you will see how the subject under tests is not just the view implementation and the tests execute production code from the activitites or fragments to the repositories. We decided not to test the rest of the code in the sample project because it is basically code already tested in the framework or the code is related to adapters for the api client, which is a third party library. If you want to review our testing approach, please review the blog posts we wrote about this topic: http://blog.karumi.com/world-class-testing-development-pipeline-for-android
Regards!
Pedro