Jetpack Compose – Don't throw your presenters off
Tags: android, jetpackcompose, mvp, mvvm
Published: 2021-05-07 15:16:28

#Prelude

In the beginning there was Context and XML all over the place, no architecture or tests what so ever, even enums weren't allowed—God have mercy.

We, as a community, came a long way to be able to be discussing things like the ones on this article. Be proud.

#Introduction

Compose is very refreshing for the Android development community, it comes with a change in paradigm to describe the UI, and this change comes with some costs, we are going to look at some ramifications that the adoption of Jetpack Compose has on the architectural patterns of our UI layer.

As of this writing we can read the following on [Jetpack Compose docs](https://developer.android.com/jetpack/compose/interop/compose-in-existing-arch)

> Unidirectional Data Flow (UDF) architecture patterns work seamlessly with Compose. If the app uses other types of architecture patterns instead, like **Model View Presenter (MVP), we recommend you migrate that part of the UI to UDF before or whilst adopting Compose**.
[emphasis added]

Google is right (as usual). It is true that MVP doesn't work well with compose out of the box, but, throwing well functioning and tested presenters (you got tests right?) off is not the only option on the table.

I'm going to present (got it?) an alternative for you out there that are still using MVP on Android and just want to convert some screen to compose while reusing your UI logic and move on with your life. In this journey we are going to use a clever technique that dates before 1994, one that all Android developers should be very familiar with.

##Disclaimer #1

>I'm not suggesting that you should stick with MVP on your projects instead of rewriting it to MVVM or similar patterns, all that I'm saying is that you don't **have to** if you don't want to.

>I'm going to assume that you are familiar with both MVP and MVVM through the article.

#Back in time a few decades

We got ourselves a problem, we have all the UI logic written in MVP, and Compose really wants to work with MVVM, what can we do about it?

Well, it is an instance of a well known problem in software engineer, it is so common that we have a pre-made solution for it, and we can found it here:

![cover of the book: design patterns, 1994](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bjougsx1of2vp1prjwr8.jpg)

Back to 1994, we are going to use the Adapter design pattern.

{% wikipedia https://en.wikipedia.org/wiki/Adapter_pattern %}

Let's give Compose want it wants. We are going to create an Adapter that will adapt MVP to MVVM.

You've read it, we are going to use a pattern to transform a pattern into another pattern. A true pattern abuse.

#But can you do it?

Yes!! Look at this masterpiece:

![a tic-tac-toe app gif](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s684bq96k0gbk5i611ft.gif)

You can grab the code here:

{% github alyssoncs/composepresenter no-readme %}

##But how?

A ViewModel is usually composed of two "parts".

It has a set of public methods which are called by the view controller (Activity/Fragment). It also has a set of observables that are observed by the same view controller.

The first part is analogous to the Presenter in MVP, so, that is going to be the first axis of adaptation we'll make.

In MVP, the View is an interface that is usually implemented by the Activity/Fragment, and that is where things change in the approach of this article. We are going to adapt the View to be the second part of the ViewModel, i.e., the observables.

So, in our solution here, the ViewModel will adapt both the Presenter and the View. It will be something like this:

[![uml diagram](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nmwdb96ji9fm9z041lrs.png)](https://mermaid-js.github.io/mermaid-live-editor/#/edit/eyJjb2RlIjoiY2xhc3NEaWFncmFtXG5cbiAgY2xhc3MgUHJlc2VudGVyIHtcbiAgICA8PGludGVyZmFjZT4-XG4gIH1cblxuICBjbGFzcyBWaWV3IHtcbiAgICA8PGludGVyZmFjZT4-XG4gIH1cblxuICBjbGFzcyBWaWV3TW9kZWwge1xuICAgIDw8YWJzdHJhY3Q-PlxuICB9XG5cbiAgY2xhc3MgTXlWaWV3TW9kZWwge1xuICB9XG5cbiAgVmlldyA8fC4uIE15Vmlld01vZGVsXG4gIFByZXNlbnRlciA8fC4uIE15Vmlld01vZGVsXG4gIE15Vmlld01vZGVsIC0tfD4gVmlld01vZGVsXG4iLCJtZXJtYWlkIjp7fSwidXBkYXRlRWRpdG9yIjpmYWxzZX0)

But I like to be more explicit, so I'm breaking the ViewModel in two, I think it make things easier to understand, but both ways will work fine.

[![uml diagram](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n7u3zk5s55jwlfwvbekq.png)](https://mermaid-js.github.io/mermaid-live-editor/#/edit/eyJjb2RlIjoiY2xhc3NEaWFncmFtXG5cbiAgY2xhc3MgUHJlc2VudGVyIHtcbiAgICA8PGludGVyZmFjZT4-XG4gIH1cblxuICBjbGFzcyBWaWV3IHtcbiAgICA8PGludGVyZmFjZT4-XG4gIH1cblxuICBjbGFzcyBWaWV3TW9kZWwge1xuICAgIDw8YWJzdHJhY3Q-PlxuICB9XG5cbiAgY2xhc3MgUHJlc2VudGVyVmlld01vZGVsV3JhcHBlciB7XG4gIH1cblxuICBjbGFzcyBWaWV3VG9WaWV3TW9kZWxBZGFwdGVyIHtcbiAgfVxuXG4gIFZpZXcgPHwuLiBWaWV3VG9WaWV3TW9kZWxBZGFwdGVyXG4gIFByZXNlbnRlciA8fC4uIFByZXNlbnRlclZpZXdNb2RlbFdyYXBwZXJcbiAgUHJlc2VudGVyVmlld01vZGVsV3JhcHBlciAtLXw-IFZpZXdNb2RlbFxuICBWaWV3VG9WaWV3TW9kZWxBZGFwdGVyIC0tfD4gVmlld01vZGVsXG4iLCJtZXJtYWlkIjp7fSwidXBkYXRlRWRpdG9yIjpmYWxzZX0)

You can see that I called the presenter part of the diagram a "Wrapper" instead of an "Adapter", That's because it actually doesn't adapt anything, it just "wraps" the existing presenter implementation (not shown in the above diagram) inside a Android `ViewModel`, that way the presenter will share the same lifecycle than the `ViewModel`, which will make things so much easier.

I have stated that you don't have to throw away your presenters, but looking at the diagram, it looks like we'll have to implement all the presenter logic all over again inside the `PresenterViewModelWrapper`. Fear no more, delegation comes to the rescue.

[![uml diagram](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/obqjhhr3i7aa3kragy9r.png)](https://mermaid-js.github.io/mermaid-live-editor/#/edit/eyJjb2RlIjoiY2xhc3NEaWFncmFtXG5cbiAgY2xhc3MgUHJlc2VudGVyIHtcbiAgICA8PGludGVyZmFjZT4-XG4gIH1cblxuICBjbGFzcyBWaWV3IHtcbiAgICA8PGludGVyZmFjZT4-XG4gIH1cblxuICBjbGFzcyBWaWV3TW9kZWwge1xuICAgIDw8YWJzdHJhY3Q-PlxuICB9XG5cbiAgY2xhc3MgUHJlc2VudGVyVmlld01vZGVsV3JhcHBlciB7XG4gIH1cblxuICBjbGFzcyBWaWV3VG9WaWV3TW9kZWxBZGFwdGVyIHtcbiAgfVxuXG4gIGNsYXNzIFByZXNlbnRlckltcGwge1xuICB9XG5cbiAgVmlldyA8fC4uIFZpZXdUb1ZpZXdNb2RlbEFkYXB0ZXJcbiAgUHJlc2VudGVyIDx8Li4gUHJlc2VudGVyVmlld01vZGVsV3JhcHBlclxuICBQcmVzZW50ZXJWaWV3TW9kZWxXcmFwcGVyIC0tfD4gVmlld01vZGVsXG4gIFZpZXdUb1ZpZXdNb2RlbEFkYXB0ZXIgLS18PiBWaWV3TW9kZWxcbiAgUHJlc2VudGVyIDx8Li4gUHJlc2VudGVySW1wbFxuICBQcmVzZW50ZXJWaWV3TW9kZWxXcmFwcGVyIC4ubyBQcmVzZW50ZXI6ZGVsZWdhdGVzIiwibWVybWFpZCI6e30sInVwZGF0ZUVkaXRvciI6ZmFsc2V9)

Let's break down this diagram for a moment.

The `PresenterViewModelWrapper` will hold an instance of a `Presenter`—which will actually be the `PresenterImpl`—that is what the diamond shaped arrow means.

Every `Presenter` method call dispatched to the `PresenterViewModelWrapper` will be dispatched further, that is, delegated to, the `Presenter` instance that it holds. And that is the way that the `PresenterViewModelWrapper` will realize its own `Presenter` implementation.

Does it seems hard to understand? Take a look at the code.

```kotlin
class PresenterViewModelWrapper(
    presenter: Presenter
) : ViewModel(), Presenter by presenter

```

>And that's, my friend, might be the reason that we were creating the `Presenter` interface this whole time instead of just creating a concrete `Presenter` class without interfaces. There are two presenters implementations now 😎

If you are not familiar with the `Presenter by presenter` part of the code, is kotlin syntactic sugar to the following code:

```kotlin
class PresenterViewModelWrapper(
    private val presenter: Presenter
) : ViewModel(), Presenter {
    override fun onStart() = presenter.onStart()
    override fun onClickItem(position: Int) = presenter.onClickItem(position)
    override fun onSaveButtonClicked() = presenter.onSaveButtonClicked()
}
```

If you don't know me, I'm a TDD practitioner (yeah, that kind of person) and even I wouldn't unit test a class like that. It's too simple to be tested.

When we build our application, we just pass our `PresenterImpl`, the one that has all the logic implemented and is well tested, to our `PresenterViewModelWrapper`.

If you like to, you can take a look at the `PresenterImpl` equivalent in my sample app [here](https://github.com/alyssoncs/composepresenter/blob/master/app/src/main/java/com/example/composepresenter/features/tictactoe/presentation/TicTacToePresenterImpl.kt).

##What about the View?

That's where the real Adapter thing happens, but is really simple as well, all we have to do is adapt every call to the `View` methods to a operation on an observable exposed by this `ViewModel`. Like so:

```kotlin
class ViewToViewModelAdapter : ViewModel(), View {
    val isLoading = MutableLiveData<Boolean>(false)
    val userScore = MutableLiveData<Int>(0)

    override fun showLoadingAnimation() {
       isLoading.value = true
    }

    override fun hideLoadingAnimation() {
       isLoading.value = false
    }

    override fun setUserScore(score: Int) {
       userScore.value = score
    }
}
```

It is a very simplistic example, so I'll show the actual Adapter that I've implemented on the sample app:

{% details Click here to see the code %}

```kotlin
class TicTacToeViewToViewModelAdapter : ViewModel(), TicTacToeView {
    companion object {
        private const val BOARD_SIZE = 3
    }

    sealed class Snackbar {
        data class InvalidMove(val x: Int, val y: Int): Snackbar()
        object Tie: Snackbar()
        object PlayerOneVictory : Snackbar()
        object PlayerTwoVictory : Snackbar()
    }

    enum class Tile {
        Empty,
        PlayerOne,
        PlayerTwo,
    }

    private val _board = List(BOARD_SIZE) { List(BOARD_SIZE) { MutableLiveData(Tile.Empty) } }
    val board: List<List<LiveData<Tile>>> = _board

    private val _playerOneScore = MutableLiveData<Int>()
    val playerOneScore: LiveData<Int> = _playerOneScore

    private val _playerTwoScore = MutableLiveData<Int>()
    val playerTwoScore: LiveData<Int> = _playerTwoScore

    private val _snackbar = MutableLiveData<MvvmEvent<Snackbar>>()
    val snackbar: LiveData<MvvmEvent<Snackbar>> = _snackbar

    private val _isBoardEnabled = MutableLiveData<Boolean>(true)
    val isBoardEnabled: LiveData<Boolean> = _isBoardEnabled

    override fun clearBoard() {
        _board.forEach { row ->
            row.forEach { tile ->
                tile.value = Tile.Empty
            }
        }
    }

    override fun enableBoard() {
        _isBoardEnabled.value = true
    }

    override fun disableBoard() {
        _isBoardEnabled.value = false
    }

    override fun setPlayerOneScore(score: Int) {
        _playerOneScore.value = score
    }

    override fun setPlayerTwoScore(score: Int) {
        _playerTwoScore.value = score
    }

    override fun updatePlayerOneTile(x: Int, y: Int) {
        _board[x][y].value = Tile.PlayerOne
    }

    override fun updatePlayerTwoTile(x: Int, y: Int) {
        _board[x][y].value = Tile.PlayerTwo
    }

    override fun notifyInvalidMove(x: Int, y: Int) {
        _snackbar.value = MvvmEvent(Snackbar.InvalidMove(x, y))
    }

    override fun notifyTie() {
        _snackbar.value = MvvmEvent(Snackbar.Tie)
    }

    override fun notifyPlayerOneVictory() {
        _snackbar.value = MvvmEvent(Snackbar.PlayerOneVictory)
    }

    override fun notifyPlayerTwoVictory() {
        _snackbar.value = MvvmEvent(Snackbar.PlayerTwoVictory)
    }
}
```
{% enddetails %}

If you look close, that isn't a lot of logic going on here, thats because we try hard to write Dumb Views™ in MV<b>X</b> patterns. It also means that I didn't feel the need to test this either, but hey, you are not me, you can test the hell out of this if you want to.

Although MVP takes a lot of logic out of the View, it still puts a lot of *state* on it, if we use a `ViewModel` as the View the state is being saved on the right place.

#Are you really using Jetpack Compose?

Of course! You can take a look [here](https://github.com/alyssoncs/composepresenter/blob/master/app/src/main/java/com/example/composepresenter/features/tictactoe/TicTacToeActivity.kt)

##Disclaimer #2

>If you decide to click on the link above, you are going to witness the worse Jetpack Compose sample on the internet.

>Seriously, I just skim through the 5 first pages of the [Compose docs](https://developer.android.com/jetpack/compose/documentation), learned about `Column`s and `Row`s, stole this poor man's [code](https://dev.to/aniketsmk/how-to-add-a-snackbar-to-jetpack-compose-1ehj), and hacked my way into that UI.

>Don't take my Compose code as an example of nothing!

There is one thing that I want to draw attention, take a look at this snippet:

```kotlin
private val presenter by viewModel<TicTacToePresenterWrapper>()
private val viewState by viewModel<TicTacToeViewToViewModelAdapter>()

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
        TicTacToe()
    }

    if (savedInstanceState == null) {
        //take a look at this line
        presenter.setView(viewState)
        presenter.onStart()
    }
}
```

Because I'm using two separate `ViewModel`s, I have to pass the one that implements the `View` to the one that implements the `Presenter`, like we would do in plain MVP. If we were using a single `ViewModel` that implemented both the `Presenter` and the `View`, we could pass itself as the view on the `setView()`, we could even do this in the `init` block of the `ViewModel`, like this:

```kotlin
init {
    setView(this)
}
```

After that, for every action, I call the `presenter.onAction()` function, and the composables just observe the `LiveData` exposed by the `viewState`.

#Beyond Jetpack Compose

This technique is not only useful when working with MVP in Compose, you can use this to work with apps that are using XML as well.

If you are familiar with MVP in Android, you might have notice that this pattern makes the presenters much simpler. There is no need to "unbind" the View when the activity is not in the suitable state, nor do we have to save the state of the Presenter or the View at every configuration change.

The presenter and the View now live on the `ViewModel` lifecycle.

#Speculation

I think that **maybe** there is value in writing your presentation logic in MVP in the first place, only to adapt to another pattern in the end. MVP is very simple, we don't need `LiveData`/`ViewModel` or fancy things to encode our view logic. 

We **might** even manage to write the presentation logic in MVP with KMM (Kotlin Multiplatform Mobile) and each platform just adapts it to the preferred pattern, like MVVM/MVI/VIPER/etc.

I'm not claiming that we can do this, because I know virtually nothing about KMM, MVI or VIPER. It might just be impossible to adapt MVP to VIPER, or I might be under a big misapprehension about what KMM can do.

I'll let this possibilities to folks that are much more versed in those concepts than me.