loading...
Cover image for Should I pass a View to a Presenter's constructor?

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

chrisvasqm profile image Christian Vasquez ・3 min read
Cover image by Ben White from Unsplash

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()
}

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
    }

    // ...

}

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()
    }

    // ...
}

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)

To:

presenter = LoginPresenter().apply { attach(this@LoginActivity) }

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()
}

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

interface Presenter {
    fun detach()
}

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

interface Detachable {
    fun detach()
}

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
    }

    // ...

}

To:

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

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

}

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

presenter = LoginPresenter()
presenter.attach(this)

Or this:

presenter = LoginPresenter().apply { attach(this@LoginActivity) }

To:

presenter = LoginPresenter(this)

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()
    }

    // ...
}

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()
    }

    // ...
}

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 :)

Discussion

pic
Editor guide
Collapse
socratesdz profile image
Sócrates Díaz

I once had the same doubts and discussed it with my peers. Thanks to that discussion we came to the conclusion that if you're using Dependency Injection, then you should use the attach method, simply because you can't inject a view which extends an Android activity.