Chapters

Hide chapters

Advanced Android App Architecture

First Edition · Android 9 · Kotlin 1.3 · Android Studio 3.2

Before You Begin

Section 0: 3 chapters
Show chapters Hide chapters

17. Model View Intent Theory
Written by Aldo Olivares

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

You have learned about many different architecture patterns at this point, including MVVM, MVI and MVC.

In this chapter, you are going to learn about a very different architecture pattern for building Android apps called MVI.

Along the way, you will learn:

  • What MVI is and how it works.
  • The different layers of the MVI architecture pattern.
  • How a unidirectional flow of an Android app works.
  • How MVI improves the testability of your app by providing predictable and testable states.
  • MVI advantages and concerns vs other architecture patterns.

What is MVI?

MVI stands for Model-View-Intent. MVI is one of the newest architecture patterns for Android. The architecture was inspired by the unidirectional and cyclical nature of the Cycle.js framework and brought to the Android world by Hannes Dorfaman.

MVI works in a very different way compared to its distant relatives such as MVC, MVP or MVVM. The role of each of its components goes as follows:

  • Model: Represents a state. Models in MVI should be immutable to ensure a unidirectional data flow between them and the other layers in your architecture.
  • Intent: Represents an intention or a desire to perform an action by the user. For every user action, an intent will be received by the View, which will be observed by the Presenter and translated into a new state in your Models.
  • View: Just like in MVP they are represented by Interfaces, which are then implemented in one or more Activities or Fragments.

Next, you’ll explore each of the layers one by one.

Model

In other architecture patterns such as MVVM or MVP, the Models act as a rather simple layer to hold your data and act as a bridge to the backend of your app such as your databases or your APIs. However, in MVI, Models have a much more important role; they not only hold data, but also represent the state of your app.

data class Movie(
  var voteCount: Int? = null,
  var id: Int? = null,
  var video: Boolean? = null,
  var voteAverage: Float? = null,
  var title: String? = null,
  var popularity: Float? = null,
  var posterPath: String? = null,
  var originalLanguage: String? = null,
  var originalTitle: String? = null,
  var genreIds: List<Int>? = null,
  var backdropPath: String? = null,
  var adult: Boolean? = null,
  var overview: String? = null,
  var releaseDate: String? = null
)
class MainPresenter(private var view: MainContract.View?) : MainContract.Presenter, MainContract.InteractorOutput {

  override fun onViewCreated() {
    view.showLoading()
    interactor
        .loadMovieList()
        .observe(view as MainActivity, Observer) {movieList ->
          movieList.let {
            this.onQuerySuccess(movieList)
          }
        }
  }

  override fun onQuerySuccess(data: List<Movie>) {
    view.hideLoading()
    view.displayMovieList(data)
  }
}
sealed class MovieState {
  object LoadingState : MovieState()
  data class DataState(val data: List<Movie>) : MovieState()
  data class ErrorState(val data: String) : MovieState()
  data class ConfirmationState(val movie: Movie) : MovieState()
  object FinishState : MovieState()
}
class MainPresenter(private var view: MainContract.View?) : MainContract.Presenter, MainContract.InteractorOutput {

  override fun onViewCreated() {
    view.render(MovieModel(true, null, null))
    interactor
        .loadMovieList()
        .observe(view as MainActivity, Observer) { movieList ->
          movieList.let {
            this.onQuerySuccess(movieList)
          }
        }
  }

  override fun onQuerySuccess(data: List<Movie>) {
    view.render(MovieModel(false, data, null))
  }
  private fun observeMovieDisplay() = movieInteractor.getMovieList()
      .observeOn(AndroidSchedulers.mainThread())
      .doOnSubscribe { view.render(MovieState.LoadingState) }
      .doOnNext { view.render(it) }
      .subscribe()
}

Views & Intents

In MVI, just like in in MVP, the Views are defined with the help of an Interface that acts as a contract which is implemented by a Fragment or an activity. The difference lies in the fact that Views in MVI tend to have a single render() method that accepts a state to render to the screen and different intent() methods as Observables that respond to user actions.

class MainActivity : MainView {
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
  }

  //1
  override fun getItemsIntent() = button.clicks()

  //2
  override fun render(state: ViewState) {
      when(state) {
          is ViewState.DataState -> renderDataState(state)
          is ViewState.LoadingState -> renderLoadingState()
          is ViewState.ErrorState -> renderErrorState(state)
      }
  }

  //4
  private fun renderDataState(dataState: ViewState.DataState) {
      //Render Data on Screen
  }

  //3
  private fun renderLoadingState() {
      //Render Loading indicator on screen
  }

  //5
  private fun renderErrorState(errorState: ViewState.ErrorState) {
      //Render Error on Screen
  }
}

State Reducers

With your usual mutable Models it is very easy to change the state of your app. Whenever you need to add, remove or update some underlying data you just need to call a method in your Models such as this:

myModel.addItems(newItems)
val myList = listOf(1, 2, 3, 4, 5)
var result = myList.reduce { accumulator, currentValue ->
  println("accumulator = $accumulator, currentValue = $currentValue")
  accumulator + currentValue
}
println(result)
accumulator = 1, currentValue = 2
accumulator = 3, currentValue = 3
accumulator = 6, currentValue = 4
accumulator = 10, currentValue = 5
15

MVI Advantages and Concerns

Just like the previous patterns, Model-View-Intent is just an additional tool that you have at your disposal to create maintainable and scalable apps.

Frequently Not Asked MVI Questions

Q. MVI and MVP look very similar…What is the main difference between the two patterns?

Key points

Where to go from here?

MVI is a powerful architecture pattern that relies on a unidirectional data flow and immutable Models to solve common concerns across Android development such as the state problem and thread safety.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now