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:
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:
The actual implementation is decoupled from the presenters here:
Then inside MainPresenter we have this code:
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:
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:
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.