Kodeco Forums

Android VIPER Tutorial

In this tutorial, you'll become familiar with the various layers of the VIPER architecture pattern and see how to keep your app modules clean and independent.


This is a companion discussion topic for the original entry at https://www.raywenderlich.com/5192-android-viper-tutorial

“router?.navigateTo(DetailActivity.TAG, joke)”

This line does what should not be done. It couples the presenter with a particular view (DetailActivity). A presenter should not reference a view.

"DetailActivity.TAG → startActivity(Intent(this@MainActivity, DetailActivity::class.java)
.putExtra(“data”, data as Parcelable)) "

This couples the calling activity (MainActivity) with another activity (DetailActivity).

IMHO SomeActivity.TAG string should not reside in SomeActivity. It should be extracted to external class. This class should have the logic which string (TAG) maps to which Activity (Android Activity class).

“private val router: Router? by lazy { BaseApplication.INSTANCE.cicerone.router }”

This line in a presenter is a huge problem. It couples a presenter to a 3rd party library (Cicerone). This compromises the Presenter’s testability and ties it to a framework specific implementation details.

Hi ggeorgip, thanks for your comment! Let me go through your comments one by one:

  • In order to get things organized and easy to follow, I have used an identifier for each View, particularly a tag defined in that precise View. As you say, this couples both Presenter and View, which is a bad practice; however, I’d like you to see that it is simply a String value. I mean, I could have used any custom value for ‘DetailActivity’, and employ it later again on the navigator. If this is the “coupling” you refer to, I think we are fine.
  • Well, although this is true, let me tell you that I think there is not much we can do on this matter. I mean, Android was built up with a particular structure that makes an Activity be the entity which triggers other Activity(s). Thus, as long as I am concerned, you will always need a ‘startActivity’ line at some point. Your proposal is fine, having this identifiers (TAG) in an external class, but at the end of the day, you will be invoking the Activity in a similar way, won’t you?
  • I am not sure whether I understand what you say. It is true I am relying in a third party library, but in this particular case, I am simply using the Application class to refer to a value declared in it, thanks to its general scope. Don’t you think this is pretty similar to what you do when using a DI feature such as Dagger2?

Please, drop me your comments so that we can work this out to our best :slight_smile:

Thanks and regards,

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.

This tutorial is more than six months old so questions are no longer supported at the moment for it. Thank you!