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