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.

Version

  • Kotlin 1.2, Android 4.4, Android Studio 3

If you have some experience with Android development, Architecture Patterns may sound like an old-fashioned concept, which some smart guy has brought to the table just to put you down mercilessly. If I add the word VIPER to the discussion, you might think this sounds like a scam or click-bait. However, I assure you that once you get in-the-loop, you will appreciate how valuable it is to have proper code structure and organization.

In this tutorial, you will get to know the VIPER architecture pattern. You will start by understanding the ideas behind VIPER and how it fits into the Android framework. You will then put it into practice, implementing a sample app that adopts this architecture pattern and illustrates its benefits.

Do not fear the VIPER, just enjoy the bite! :]

Why are you (probably) reading this?

If the above introduction has grabbed your attention, it means you may feel as I used to just a few months ago. To sum up, you may have been developing Android apps for quite some time, reaching a high-level of code complexity and a considerable amount of lines of code in some of your projects. Then, at some point, you have gone through any of the following situations:

  1. You get lost refactoring your own code when adding extra features.
  2. You realize that there are too many null checks of the same field in different parts of the code.
  3. Your classes and/or methods have increased their line length dramatically, largely exceeding the Rule of 30.
  4. Some parts of your code are difficult or even impossible to cover with Unit Tests.
  5. Your code becomes illegible to any other developer, since you are the only one able to “decipher” your business logic.
  6. All of the above. :]

In my case, I was lucky enough to have a colleague around who pushed me to adopt architecture patterns in my projects. Please let me be that colleague for you.

When Android I met Architecture Patterns…

Let me start this section making an announcement upfront: I do not aim to put the blame on Android/Google for the lack of architecture patterns in my early projects. That responsibility is only mine.

It wasn't me!

It wasn’t me!

On the other hand, my impression is that, until recently, architecture patterns were never a main focus of the official Android documentation. In the old days, app structure was based around the four Android main application components: Activity (most cases), Service, Broadcast Receiver, and/or Content Provider. Later, Fragments came on the stage and became mainstream in many applications.

It was only last year that Google introduced the Android Architecture Blueprints project, in an attempt to give Android developers some organization and structure guidelines. You should definitely have a look at the repo to get a feeling for what’s available as guidance. In addition, Google also released the Android Architecture Components, which is “a collection of libraries that help you design robust, testable, and maintainable apps”. The Architecture Components are now part of Android Jetpack.

VIPER at a glance

In this section you will start diving into VIPER, an architecture pattern related to the Clean Architecture Paradigm. VIPER stands for View, Interactor, Presenter, Entity, and Router. This five-layer organization aims to assign different tasks to each entity, following the Single Responsibility Principle. The basic idea behind VIPER and other Clean Architecture patterns is to create a cleaner and more modular structure to isolate your app’s dependencies and improve the flow of data within your app.

VIPER scheme

Within the framework of an Android app, the VIPER layers are assigned according to the following scheme:

  • The View corresponds to an Activity or Fragment in the app. A goal is to make the View as dumb as possible, so that it only takes care of showing the UI.
  • The Interactor takes care of performing any action, when the Presenter says to.
  • The Presenter acts as a “Head-of-Department”. In other words, it commands any action making use of the Interactor, tells the View to display content, and orders the navigation to other screens using the Router.
  • The Entity represents the app data. In short, it acts likes the Model in the MVP architecture pattern.
  • The Router handles navigating to other screens during the app lifecycle.

Further theoretical details on this architecture pattern can be found in the following excellent reference.

Getting started

Enough talk! From this section onwards you will be creating an app named Chucky Facts. The goal is to give you a taste of VIPER in a real app, so that you can see how to handle certain common scenarios under this architecture.

Begin by downloading the starter project using the download button at the top or bottom of the tutorial. The starter project contains the basic skeleton app and some assets.

Chucky Facts project structure

For didactic purposes, the code has been organized according to VIPER layer names (except for the Router). In a real application, it is common to structure the code in modules; in this particular example, they could be “SplashModule, “MainModule”, and “DetailModule”, for instance.

Build and run the starter project.

The starter app

As you can see, there’s not a whole lot going on in the app yet.

App definition and description

This app is just a simple viewer which displays information fetched from a REST API. The data source is the well-known Internet Chuck Norris Database, which provides a large list of “facts” about Chuck Norris and his superior status.

The starter AndroidManifest.xml file shows three Activity items, one being SplashActivity which includes the MAIN intent-filter. Both MainActivity and DetailActivity will later extend from BaseActivity, which lets them include a Toolbar.

The app skeleton also includes the Joke class, in the entity package, and JokesListAdapter in the view.adapters package. These implementations do not directly relate to the topic of this tutorial, but feel free to have a look at them and analyze their behavior.

You can take some time to inspect the rest of the starter project and all the features included out-of-the-box (for example, the resource files strings.xml, dimens.xml, and styles.xml).

App modules and entities

In this tutorial, every module consists of a contract and several associated classes which implement the various VIPER layers. The contract describes which VIPER layers must be implemented in the module and the actions the layers will perform.

Have a quick look at the contract corresponding to the Splash Module:

interface SplashContract {
  interface View {
    fun finishView()
  }

  interface Presenter {
    // Model updates
    fun onViewCreated()
    fun onDestroy()
  }
}

You can see there are two layers, defined as interfaces, that must be implemented: View and Presenter. Obviously, the functions declared inside the interfaces will be defined at some point in the code.

Main Module

View

Starting with MainActivity, make it extend BaseActivity() (instead of AppCompatActivity()), and implement the interface MainContract.View.

class MainActivity : BaseActivity(), MainContract.View {

After that, you will be required to implement some missing members. Hit Ctrl+I, and you will see that only one method corresponds to BaseActivity(), whereas the rest belong to MainContract.View.

MainActivity missing members

Before populating these methods, add a few properties:

private var presenter: MainContract.Presenter? = null 
private val toolbar: Toolbar by lazy { toolbar_toolbar_view } 
private val recyclerView: RecyclerView by lazy { rv_jokes_list_activity_main } 
private val progressBar: ProgressBar by lazy { prog_bar_loading_jokes_activity_main } 

You’ll need to hit Alt+Enter on PC or Option+Return on Mac to pull in the imports, and be sure to use the synthetic Kotlin Android Extensions for the view binding. Note that the presenter corresponds to the same module as the current view.

Now, you can fill the overridden functions in as follows:

override fun getToolbarInstance(): Toolbar? = toolbar

This simply returns the Toolbar instance present in the layout.

Add overrides for the loading functions:

override fun showLoading() {
  recyclerView.isEnabled = false
  progressBar.visibility = View.VISIBLE
}

override fun hideLoading() {
  recyclerView.isEnabled = true
  progressBar.visibility = View.GONE
}

The above two functions handle the data loading, showing and hiding the ProgressBar instance included in the layout.

Next, override showInfoMessage using the toast() method from Anko Commons.

override fun showInfoMessage(msg: String) {
  toast(msg)
}

This is a simple function to show some info to the user.

Add an override for publishDataList():

override fun publishDataList(data: List<Joke>) {
  (recyclerView.adapter as JokesListAdapter).updateData(data)
}

This last function updates the RecyclerView instance data.

Do not forget to initialize the presenter (you will define it shortly) and to configure the RecyclerView; add the following in onCreate():

presenter = MainPresenter(this)
recyclerView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
recyclerView.adapter = JokesListAdapter({ joke -> presenter?.listItemClicked(joke) }, null)

Finally, it is common when doing VIPER, that you inform the presenter when the view is visible, and make the instance null when the Activity is being destroyed. Add the following two lifecycle method overrides:

override fun onResume() {
  super.onResume()
  presenter?.onViewCreated()
}

override fun onDestroy() {
  presenter?.onDestroy()
  presenter = null
    super.onDestroy()
}

Note: you can use a Dependency Injection approach, such as Dagger 2, Kodein, or Koin to avoid making instances null before finishing in order to prevent memory leaks.

Presenter

Now you can define MainPresenter as follows, and place it in the presenter folder:

class MainPresenter(private var view: MainContract.View?)
    : MainContract.Presenter, MainContract.InteractorOutput {   // 1

  private var interactor: MainContract.Interactor? = MainInteractor()   // 2

  override fun listItemClicked(joke: Joke?) {   // 3
  }

  override fun onViewCreated() {   // 4
  }

  override fun onQuerySuccess(data: List<Joke>) {   // 5
  }

  override fun onQueryError() {
  }

  override fun onDestroy() {   // 6
  }
}

To define the implementation, you have to take into account that:

  1. The class implements two interfaces declared in the MainContract: Presenter, which commands the whole module, and InteractorOutput, which allows you to define actions in response to what the Interactor returns.
  2. You will need an Interactor instance to perform actions of interest.
  3. When this function is called, the application will navigate to another screen (Detail Module) passing any data of interest; thus, it will be using the Router.
  4. This callback defines what will happen when the view is finally loaded. Normally, you define here anything which happens automatically, with no user interaction. In this case, you will try to fetch data from the datasource through the REST API.
  5. This function and the following will define what happens when the database query is addressed.
  6. As for the presenter in the view, you need to make the presenter properties null in order to avoid any trouble when the system kills the module.

Setting aside the Router for further implementation, you can actually fill in the rest of functions.

For onQuerySuccess(data: List), add

view?.hideLoading()
view?.publishDataList(data)

and for onQueryError(),

view?.hideLoading()
view?.showInfoMessage("Error when loading data")

You simply handle the success or the error of the data query.

In onDestroy(), add

view = null
interactor = null

You make the properties null, as you did in the view layer.

Interactor

For onViewCreated() in the presenter, you want to query data from the remote datasource once the view loads. You need to first create the MainInteractor class in the interactor package.

class MainInteractor : MainContract.Interactor {   // 1

  companion object {
    val icndbUrl = "https://api.icndb.com/jokes"
  }

  override fun loadJokesList(interactorOutput: (result: Result<Json, FuelError>) -> Unit) {   // 2
    icndbUrl.httpPost().responseJson { _, _, result ->   // 3
      interactorOutput(result)
    }
  }
}

Pull in dependencies via Alt+Enter on PC and Option+Return on Mac using import statements that begin with com.github.kittinunf. Of note in MainInteractor are the following:

  1. Do not forget to make the class implement the proper interface, from the module contract.
  2. loadJokesList() is a function which requires a lambda as input argument; this is technically the same as having a typical Java Callback.
  3. In order to query data, the application uses Fuel, which is a Kotlin/Android library that allows easy and asynchronous networking. Another alternative would be using the well-known Retrofit. The query result is directly used by the lambda as input argument.

Do you remember the onViewCreated() function in MainPresenter that you left empty in the previous section? It is time now to fill it in.

override fun onViewCreated() {
  view?.showLoading()
  interactor?.loadJokesList { result ->
    when (result) {
      is Result.Failure -> {
        this.onQueryError()
      }
      is Result.Success -> {
        val jokesJsonObject = result.get().obj()

        val type = object : TypeToken<List<Joke>>() {}.type
        val jokesList: List<Joke> =
            Gson().fromJson(jokesJsonObject.getJSONArray("value").toString(), type)

        this.onQuerySuccess(jokesList)
      }
    }
  }
}

As you see, you only have to handle the possible results of the query. Bear in mind that Result.Success returns a JSON object that has to be parsed somehow; in this case, you’re using Gson.

Note: remember that when parsing a JSON stream, you first need to know how data is organized. In this case, “value” is the root keyword of the array.

Build and run the app, and you’ll be greeted with a set of illuminating facts:

Detail Module

The detail module implementation is similar to the previous one: you only need each class to extend from the proper parent and implement the right interfaces according to the module contract.

View

Update the DetailActivity declaration to be:

class DetailActivity : BaseActivity(), DetailContract.View {
    // ...

Add the following properties and companion object:

companion object {
  val TAG = "DetailActivity"
}

private var presenter: DetailContract.Presenter? = null
private val toolbar: Toolbar by lazy { toolbar_toolbar_view }
private val tvId: TextView? by lazy { tv_joke_id_activity_detail }
private val tvJoke: TextView? by lazy { tv_joke_activity_detail }

Instantiate the presenter in onCreate():

presenter = DetailPresenter(this)

Override the view interface methods as follows:

override fun getToolbarInstance(): android.support.v7.widget.Toolbar? = toolbar

override fun showJokeData(id: String, joke: String) {
    tvId?.text = id
    tvJoke?.text = joke
}

override fun showInfoMessage(msg: String) {
   toast(msg)
}

Configure the toolbar back button by overriding onOptionsItemSelected():

override fun onOptionsItemSelected(item: MenuItem?): Boolean {
  return when (item?.itemId) {
    android.R.id.home -> {
      presenter?.backButtonClicked()
      true
    }
    else -> false
  }
}

The Router will bring arguments to this Activity from the previous module (remember listItemClicked(joke: Joke?) in the Main Module presenter), and they are retrieved and passed to the presenter once the view is ready. Add the following lifecycle overrides:

override fun onResume() {
  super.onResume()
  // add back arrow to toolbar
  supportActionBar?.let {
    supportActionBar?.setDisplayHomeAsUpEnabled(true)
  }
  // load invoking arguments
  val argument = intent.getParcelableExtra<Joke>("data")
  argument?.let { presenter?.onViewCreated(it) }
}

override fun onPause() {
  super.onPause()
}

We’ll finish these overrides in the next main section.

Presenter

To complete the classes in this module, add the following class to the presenter package:

class DetailPresenter(private var view: DetailContract.View?) : DetailContract.Presenter {

  override fun backButtonClicked() {
  
  }

  override fun onViewCreated(joke: Joke) {
    view?.showJokeData(joke.id.toString(), joke.text)
  }

  override fun onDestroy() {
    view = null
  }
}

You can see that the backButtonClicked function is not defined yet, since it needs the module Router, whose implementation is pending.

Shaping the router

In some parts of this tutorial the term Router has appeared. However, you have never addressed its implementation, but why? The reason relates to how Android is designed. As mentioned before, the Router is the VIPER layer in charge of the navigation across the app views. In other words, the router is aware of every view existing in the app, and possesses the tools and resources to navigate from one view to another and vice-versa.

Keeping this in mind for each module, it would make sense to create the Presenter (the brain of the module), and then the rest of entities (View, Interactor, and Entity). Finally, the Router should wrap the Views up and somehow let the Presenter command the navigation.

This sounds perfectly reasonable, but things are a bit more complicated on Android. Think of what is the entry point of any Android application (corresponding to the the “main” method in other applications).

Yes, you are right, the entry point is that Activity which is marked with the appropriate intent-filter in the Android Manifest. In fact, every module is always accessed through an Activity, i.e. a View. Once created, you can instantiate any class or entity from it.

Therefore, the way Android has been designed makes it much more difficult to implement VIPER. At the end of the day, you will need a startActivity() function call to move to a new screen. The question that follows would be: can this be done in effortless way for the developer?

It’s possible that the new Jetpack Navigation Controller will help in this regard. But Jetpack is still in alpha, so, in the meantime, we’ll turn to a different tool.

Guided by a Cicerone

Having reached this point, it is important that you get to know Cicerone. I came across this excellent library not long ago, when trying to improve a VIPER implementation on Android. Although it is not flawless, I believe it comprises a few tools and resources which help to keep things neat and clear. It helps you make VIPER layers as decoupled as possible.

In order to use this library, there are a few steps to accomplish:

  • Add the library dependency to the application build.gradle file.
    dependencies {
      // ...
      implementation 'ru.terrakok.cicerone:cicerone:2.1.0'
      // ...
    }
    
  • Create your own Application instance in the app root package and enable Cicerone:
    class BaseApplication : Application() {
    
      companion object {
        lateinit var INSTANCE: BaseApplication
      }
    
      init {
        INSTANCE = this
      }
    
      // Routing layer (VIPER)
      lateinit var cicerone: Cicerone<Router>
    
      override fun onCreate() {
        super.onCreate()
        INSTANCE = this
        this.initCicerone()
      }
    
      private fun BaseApplication.initCicerone() {
        this.cicerone = Cicerone.create()
      }
    }
    
  • Then in your Android Manifest, set the application name to your new class:
    ...
    <application
        android:name=".BaseApplication"
    ...
    

Now, we are ready to use Cicerone. You only need to make any View aware of this new partner.

The best option is to add this line to onResume():

BaseApplication.INSTANCE.cicerone.navigatorHolder.setNavigator(navigator)

and this other one to onPause()

BaseApplication.INSTANCE.cicerone.navigatorHolder.removeNavigator()

Go ahead and do so in MainActivity and DetailActivity now.

But, what is navigator? Well, according to the documentation, it is a class property that defines exactly what happens when the Router is invoked.

Let's see an example for MainActivity. Add both a companion object and the navigator property:

companion object {
  val TAG: String = "MainActivity"   // 1
}

private val navigator: Navigator? by lazy {
  object : Navigator {
    override fun applyCommand(command: Command) {   // 2
      if (command is Forward) {
        forward(command)
      }
   }

   private fun forward(command: Forward) {   // 3
     val data = (command.transitionData as Joke)

     when (command.screenKey) {
       DetailActivity.TAG -> startActivity(Intent(this@MainActivity, DetailActivity::class.java)
           .putExtra("data", data as Parcelable))   // 4
       else -> Log.e("Cicerone", "Unknown screen: " + command.screenKey)
       }
    }
  }
}

Here's what's going on:

  1. The View needs a TAG as identifier.
  2. By default, applyCommand() handles the navigation logic. In this case, only Forward commands are processed.
  3. forward is a custom function which undertakes navigation when command.screenKey matches.
  4. At the end of the day, and due to Android design, you are going to need a startActivity somewhere, so that the navigation actually takes place.

So, you may be thinking this is just a wrapper that adds boilerplate to your code to do exactly what you used to do before. No doubt on the boilerplate, but now in any presenter, you can have a class member like

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

and use it easily. Go ahead and add it to both MainPresenter and DetailPresenter.

Do you remember listItemClicked(joke: Joke?) in MainPresenter? Now you are in position to add this beautiful line:

router?.navigateTo(DetailActivity.TAG, joke)
Magic!

Magic!

It is time to implement the Router layer for every module, even for the Splash Module.

Try to do it yourself as an exercise, and check the final project if needed. As a hint, the navigator in SplashActivity looks a lot like the one in MainActivity. And the navigator in DetailActivity looks like:

private val navigator: Navigator? by lazy {
  object : Navigator {
    override fun applyCommand(command: Command) {
      if (command is Back) {
        back()
      }
    }

    private fun back() {
      finish()
    }
  }
}

App performance analysis

Once completed, you should have Chucky Facts up and running as in the video.

When started, the splash-screen appears for just a few seconds, and then it jumps directly to MainActivity. A ProgressBar appears while the list gets populated. You can gracefully scroll along the list, with no lag whatsoever. The Router works seamlessly allowing smooth transitions between screens, even handling the Toolbar "Back Arrow" events.

Testing the snake

One of the main benefits of using architecture patterns like VIPER is that it allows you to isolate the business logic in the Presenter. This entity ends up knowing nothing about Android. This is rather convenient since it makes unit testing much easier, reducing the amount of mocks you need to create.

Although there is not much business logic in this sample app, it is always a good practice to include some tests (both unit and UI tests). For that reason, the starter app comes with a pair of UI tests in MainActivityInstrumentedTest, in the androidTest folder:

class MainActivityInstrumentedTest {

  @Rule
  @JvmField
  val activityTestRule = ActivityTestRule<MainActivity>(MainActivity::class.java)

  @Test
  fun testRecyclerViewIsPopulated() {  // 1

    waitForSplashScreen()

    onView(withId(R.id.rv_jokes_list_activity_main))
        .check(matches(hasDescendant(withText("2"))))
  }

  @Test
  fun testRecyclerViewItemClickLaunchesDetailActivity() { // 2

    waitForSplashScreen() // 3

    onView(withId(R.id.rv_jokes_list_activity_main))
        .perform(RecyclerViewActions.scrollToPosition<JokesListAdapter.ViewHolder>(2))
        .perform(RecyclerViewActions.actionOnItemAtPosition<JokesListAdapter.ViewHolder>(2, click()))

    assert(onView(ViewMatchers.withId(R.id.rv_jokes_list_activity_main)) == null)
  }
}

Regarding the above snippet:

  1. The first test assesses that the RecyclerView gets populated, particularly checking that there is an item showing a "2" text message. In this particular case, it relates to the second item of the list.
  2. The second test checks whether DetailActivity launches when clicking on a list item. For that purposes, the function assesses that once clicked, the RecyclerView does not show anymore.
  3. In both test functions there is a call to waitForSplashScreen() from Utils.kt, which sleeps the thread for a few seconds to skip the splash-screen lapse.

Go ahead and click the green test arrow next to MainActivityInstrumentedTest. The Espresso UI tests will run on a device or emulator and you should see them both pass.

Where To Go From Here?

You can download the fully finished sample project using the download button at the top or bottom of the tutorial.

There are lots of good references to read about architecture patterns and VIPER in particular. I personally found rather solid foundations reading articles like this one, and this other.

Taking into account the latest novelties on Android, it would be interesting for you to get your hands on the Android Architecture Components, especially on components like Room, LiveData, and Navigation Controller. Give the official documentation a go, since as usual it is superb.

If you are interested in wrapping up the sample app you have just finished, it would be interesting to incorporate Dependency Injection using a library like Dagger. That will add completeness to the example, and will also help you to get consistency on your development projects by including one of the most popular frameworks for creating well-structured code.

We hope you enjoyed this tutorial on the VIPER architecture pattern, and if you have any questions or comments, please join the forum discussion below!

Contributors

Comments