Hi Pablo. First of all, thank you for this great post! I’m sorry I started with criticism. Actually your post is really helpful and I’ve taken some ideas into my own projects. I’m still researching how Uncle Bob’s Clean Architecture can be applied in Android’s context. What you’ve done is awesome!
I made several modifications to you code. Can I publish them with reference to your blog post on GitHub?
The changes I did are the following:
I removed each activity’s tag. I moved the whole navigation logic inside BaseActivity. It now looks like this:
https://gist.github.com/ggeorgip/49159e5ff4fce70d171c067caecaf364
Note that the activity’s class is obtained from the string of the activity itself through reflection. I know this is far from optimal, but results in decoupling and is pretty simple.
I decoupled the Cicerone implementation following the Dependency Inversion Principle. The interface is defined inside the presenter package and is used by all presenters:
https://gist.github.com/ggeorgip/919b86f0ad06f45892a464c3b0a991d4
The actual implementation is decoupled from the presenters here:
https://gist.github.com/ggeorgip/2ce4d9ed7c3799cf7358d70646a3986f
Then inside MainPresenter we have this code:
https://gist.github.com/ggeorgip/415ffc08ccd547091d68ec1ff643d11d
Note that we reference now the activity just by its name, no coupling. The code inside BaseActivity knows how to instantiate it.
Now let’s handle the last piece of the puzzle - the data which each presenter passes to the next presenter. The views (activities) don’t have to know about it. They are just delivery mechanism and by default uses an Intent with a Bundle inside it holding primitive data types or serializable/parcalable data. Cicerone offers a neat alternative - just an object. Let’s use it. Take a look at BaseActivity:13. It uses the class ActivityDataMarshaller. Each time a new activity is about to get started, we cache in the class ActivityDataMarshaller the object that might be passed:
https://gist.github.com/ggeorgip/f601e4d5b7ffff83ad8e8bf141f0961a
So now when the user clicks on some box inside MainActivity, the MainPresenter gives the Joke object to the IRouter (through an interface), the implementation saves the destination activity’s name (Details) + the Joke as Cicerone’s Forward object, then the DetailsActivity is launched, the Joke is obtained as data from ActivityDataMarshaller and passed to DetailsPresenter:
https://gist.github.com/ggeorgip/f866fa81fafa5ebd48401f6b6a0cefe7
At the end each activity becomes much simpler with no navigation code at all. The navigation becomes generic and no activity references another activity at all. Also all presenters now reference only the presentation layer with no reference to implementation details like Cicerone and no reference to any views (activities). The BaseActivity is the only class with navigation logic and it doesn’t reference any particular activity at all (well, it uses reflection to do so with some assumptions). This is far from optimal, but is a nice discussion point.
Also instead of passing strings for the next activity to be started, Kotlin sealed classes can be used for keeping it strongly typed. I will update my example in the future.
The Presenter layer is now testable, it is decoupled and the navigation is generic and reusable without modifying any classes when a navigation change is introduced.
If you allow me to post the code in GitHub with reference to your article of course, I will be able to present my case far better with a working app, ready to be browsed and discussed.