MVP without Contracts

In this post, I want to show you how we implemented the MVP (Model View Presenter Pattern) without using a contract to improve our development speed. Before I gonna start, I have to speak verbosely when and why we started to implement the MVP and why we made the decision to not implement the typical MVP contract abstraction layer. The app and thereby the code examples are completely written in Kotlin, so if you are not (yet) familiar with Kotlin, you can check out the Android developer docs: https://developer.android.com/kotlin/resources.html
Now let’s start!
SprIT – a legacy
A couple of months ago, we reimplemented and re-released our free gas station app SprIT for Android. There were urgent reasons why we decided to reimplement the app from scratch. The app was initially developed in 2013 and therefore the code was born in the old Jelly Bean and KitKat days.
Dealing with async tasks, no Marshmallow permission management, no separation of view, presenter and business logic, no Material Design etc.
For sure it is impossible to use and adapt every new cool library, framework or pattern but the code was light years behind modern Android development.
Introducing an architectural pattern
One of our first actions was to apply an architectural pattern in our app. We decided to implement the most common used pattern for Android *drumroll* The Model View Presenter Pattern (MVP) .
The contract
MVP Android implementations are well-known to define additional interfaces for views and presenters as contracts. The presenter only receives the interface to decouple the presenter from the actual given view, et vice versa.
It all starts with the abstraction layer:
1 |
interface AbstractView |
1 2 3 4 5 6 7 8 9 10 11 12 |
abstract class AbstractPresenter<in V : AbstractView> { protected var view: V? = null fun attachView(view: V) { this.view = view } fun detachView() { view = null } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
abstract class AbstractActivity<in V : AbstractView, P : AbstractPresenter<V>>: AppCompatActivity(), AbstractView { protected abstract var presenter: P override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) presenter.attachView(this as V) } override fun onDestroy() { super.onDestroy() presenter.detachView() } } |
Three abstraction files later, the actual implementation of a SettingsView and its presenter can actually start:
1 2 3 4 |
interface SettingsView: AbstractView { fun sendFeedback() } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
class SettingsActivity: AbstractActivity<SettingsView, SettingsPresenter<SettingsView>>, SettingsView { override var presenter: SettingsPresenter = SettingsPresenter() val preferences: PreferenceFragment = SettingsFragment() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.settings_activity) fragmentManager.beginTransaction() .replace(R.id.settings_container, preferences) .commit() } fun sendFeedback() = Intent(Intent.ACTION_SENDTO).apply { data = Uri.parse("mailto:our-apps-support(at)novatec.de") putExtra(Intent.EXTRA_SUBJECT, "Feedback SprIT Android (${BuildConfig.VERSION_NAME})") }.let { Intent.createChooser(it, getString(R.string.send_feedback)) } } |
1 2 3 4 5 6 |
class SettingsPresenter: AbstractPresenter<SettingsView> { init { view?.preferences.findPreference("pref_feedback").intent = view?.sendFeedback() } } |
No groundbreaking enlightenment in 2018, it’s just the usual way how to implement MVP.
MVP – No contract!
We resigned to create the contract abstraction layer. Why? In our case every presenter corresponds to one specific view. Specifying an additional interface results in redundant code.
Don’t get us wrong: When a presenter is required by several views or a view requires different presenter implementations, the contract is totally justified. But defining an interface for a single implementation? There are pros and cons to design interfaces for that.
Pros:
- Helps to design the communication in a cleaner way
- Living documentation what the view and the presenter actually do between each other
Cons:
- More boilerplate code
- More maintenance overhead in case a contract changes
Our opinion
In our humble opinion, the implementation of the interaction between view and presenter and its unit tests are a already a living sufficient documentation. Designing an interface is of good assistance for defining the communication but in our particular case for a small app and a small developer team it is additional overhead we personally did not need.
Implementation
Below the final implementation without any contracts. To attach and detach presenters, we are using RxLifecycle to make this step more convenient and reactive.
1 2 3 4 |
abstract class AbstractActivity: RxAppCompatActivity() { fun start(): Observable<ActivityEvent> = lifecycle().filter { it == START } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
class SettingsActivity: AbstractActivity() { val preferences: PreferenceFragment = SettingsFragment() init { SettingsPresenter(this) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.settings_activity) fragmentManager.beginTransaction() .replace(R.id.settings_container, preferences) .commit() } fun sendFeedback() = Intent(Intent.ACTION_SENDTO).apply { data = Uri.parse("mailto:our-apps-support(at)novatec.de") putExtra(Intent.EXTRA_SUBJECT, "Feedback SprIT Android (${BuildConfig.VERSION_NAME})") }.let { Intent.createChooser(it, R.string.send_feedback) } } |
1 2 3 4 5 6 7 8 9 10 |
class SettingsPresenter(private val view: SettingsActivity) { init { view.start() .compose(view.bindUntilEvent(ActivityEvent.DESTROY)) .subscribe { preferences.findPreference("pref_feedback).intent = view.sendFeedback() } } } |
Testing without contracts
By omitting contracts, we have to verify Android SDK calls directly (we are using the Mockito-Kotlin library by Niek Haarman https://github.com/nhaarman/mockito-kotlin btw).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
@RunWith(MockitoJUnitRunner::class) class SettingsPresenterTest { val subject: BehaviorSubject<ActivityEvent> = BehaviorSubject.createDefault(START) val intent = mock<Intent>() val pref = mock<Preference>() val view: SettingsActivity = mock { on { start() } doReturn subject on { sendFeedback() } doReturn intent } @Test fun `send Feedback`() { SettingsPresenter(view) verify(pref).intent = intent } } |
Indeed, there is no abstraction and it can result in additional mocking for Android classes and thereby code we do not own. On the other hand, we can already technically verify that we are invoking the right (mocked) Android calls in unit tests and don’t have to wait for UI tests to get the first feedback for that. We think that there is a benefit to check that we trigger the right Android calls with the right values at the right time by the presenter in our presenter logic.
Conclusion
Omitting the Android MVP contract approach is a radical design decision we’ve made so far compared to the other tutorials and best practices guidelines out there.
Because every presenter maps one view, we think it boosts our development speed by reducing boilerplate code instead of defining interfaces.
Dear community, what do you think of MVP without contracts? Maybe you have some cool reasons why or when it should still be recommended using contracts!
Stefan
Recent posts






Comment article