Should I pass a View to a Presenter's constructor?

Christian Vasquez - Nov 5 '18 - - Dev Community
Cover image by [Ben White](https://unsplash.com/@benwhitephotography) from [Unsplash](https://unsplash.com)

I've seen multiple variations of how to implement the MVP (Model-View-Presenter) pattern on Android and one of the things that got my attention was that the way the Presenter's View is set or initialized varies from author to author.

Here's an example of what I'm saying:

Let's say we have a LoginActivity. In it's onCreate method, we will have to set the Presenter and also pass in a reference to the LoginActivity so it can manipulate it.

But, before we do that we should make our LoginActivity implement an interface, let's call it LoginView, which will be the Type of the object our LoginPresenter will use.

Presenter Example 1

First, we make a base presenter interface that will be used by every other specific presenter class:

interface Presenter<V> {
    fun attach(view: V)
    fun detach()
}
Enter fullscreen mode Exit fullscreen mode

Then, we implement it in our LoginPresenter:

class LoginPresenter : Presenter<LoginView> {

    private var view: LoginView? = null

    override fun attach(view: LoginView) {
        this.view = view
    }

    override fun detach() {
        view = null
    }

    // ...

}
Enter fullscreen mode Exit fullscreen mode

And since we have our attach() and detach() methods, our LoginActivity would look like this:


class LoginActivity : AppCompatActivity(), LoginView {

    private lateinit var presenter: Presenter<LoginView>

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        presenter = LoginPresenter()
        presenter.attach(this)
    }

    override fun onDestroy() {
        super.onDestroy()
        presenter.detach()
    }

    // ...
}

Enter fullscreen mode Exit fullscreen mode

Now, since I'm using Kotlin for this example we can make use of the apply method and make it go from this:

presenter = LoginPresenter()
presenter.attach(this)
Enter fullscreen mode Exit fullscreen mode

To:

presenter = LoginPresenter().apply { attach(this@LoginActivity) }
Enter fullscreen mode Exit fullscreen mode

Which helps, but still...

My problem with this approach is that if for whatever reason the developer forgets to use the attach() method will face unexpected results that can lead to a frustrating set of minutes/hours trying to find the bug.

Which may or may not be me a few days ago 😅

So, in order to prevent this, I thought it would be better to do the following:

Presenter Example 2

We can remove the attach() method from the Presenter interface, which would leave us with:

interface Presenter<V> {
    fun detach()
}
Enter fullscreen mode Exit fullscreen mode

And since the V is not being used, we can also remove it.

interface Presenter {
    fun detach()
}
Enter fullscreen mode Exit fullscreen mode

Now, I would like to rename this interface with the -able naming convention, which would be:

interface Detachable {
    fun detach()
}
Enter fullscreen mode Exit fullscreen mode

Examples of interfaces that use this naming convention are: Runnable, Serializable, Readable and Parceable.

This change would also require us to change our LoginPresenter from this:

class LoginPresenter : Presenter<LoginView> {

    private var view: LoginView? = null

    override fun attach(view: LoginView) {
        this.view = view
    }

    override fun detach() {
        view = null
    }

    // ...

}
Enter fullscreen mode Exit fullscreen mode

To:

class LoginPresenter(private var view: LoginView?) : Detachable {

    override fun detach() {
        this.view = null
    }

}
Enter fullscreen mode Exit fullscreen mode

So, now all we have left to do is to change our LoginActivity code from this:

presenter = LoginPresenter()
presenter.attach(this)
Enter fullscreen mode Exit fullscreen mode

Or this:

presenter = LoginPresenter().apply { attach(this@LoginActivity) }
Enter fullscreen mode Exit fullscreen mode

To:

presenter = LoginPresenter(this)
Enter fullscreen mode Exit fullscreen mode

So our entire LoginActivity class would be:

class LoginActivity : AppCompatActivity(), LoginView {

    private lateinit var presenter: LoginPresenter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        presenter = LoginPresenter(this)
    }

    override fun onDestroy() {
        super.onDestroy()
        presenter.detach()
    }

    // ...
}
Enter fullscreen mode Exit fullscreen mode

And for those that are not fan of lateinit properties:

class LoginActivity : AppCompatActivity(), LoginView {

    private val presenter = LoginPresenter(this)

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

    override fun onDestroy() {
        super.onDestroy()
        presenter.detach()
    }

    // ...
}
Enter fullscreen mode Exit fullscreen mode

Now you we can all make sure that our LoginPresenter will always be in a valid state when we use it.


I may have overlooked something with the Example 2 approach, which is why I would like to know your thoughts :)

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .