Skip to content

(MVP) Model View Presenter

Devrath edited this page Nov 30, 2023 · 15 revisions

Improvement from MVC to a better Architecture

To overcome the drawbacks of MVC controller overloading with the business logic. Modifications were done to segregate it to a better representation of architecture called Model-View-Presenter.


MVP block diagram

  • Different people use different variations of the MVP pattern.
  • Common and the most popular type of MVP pattern is by using a contract between the view and the presenter
  • The contract is an interface between the view and the presenter
  • The view calls a method to initiate an action in the presenter, the action can be to fetch the data from the model.
  • Finally once the data is retrieved from the model the presenter notifies the view via the callback from the contract.

MVP + Repository Pattern block diagram

  • A repository is used to manage the data from API and loacal database.
  • We condider Model = Repository + API + Local Database

Using the android architecture component in MVP

  • Why not - of course, yes.
  • Instead of the contract that exists between view and presenter, We can use a LiveData

Code Summary


Setting up the contract between View and the Presenter

  • Common Interface as contract -> We create an interface class, The interface created has 2 nested children interfaces.
interface AllCreaturesContract {

  interface Presenter {
    fun getAllCreatures(): LiveData<List<Creature>>
    fun clearAllCreatures()
  }

  interface View {
    fun showCreaturesCleared()
  }
}
  • Through the Presenter interface view sends the action to the presenter class.
    • We can take advantage of the android life cycle component called LiveData to update the view class since the Live data is lifecycle aware.
  • Through the view interface the presenter communicates the callback to the view class.
  • We have the contract implemented in the concrete classes.
  • The advantage of having this is when testing we can mock the view interface when writing tests for the presenter classes.
  • Since the presenter is not directly dependent on android SDK directly, we can write pure unit tests for the presenter.

Defining the base presenter for the concrete classes

abstract class BasePresenter<V> {
  private var view: WeakReference<V>? = null
  fun setView(view: V) {
    this.view = WeakReference(view)
  }
  protected fun getView(): V? = view?.get()
}
  • The base presenter has all the common code that all the presenters need to be hooked up with the view.
  • The base presenter takes the generic view V that the base presenter communicates with.
  • We use the weak reference because the concrete view is an activity\fragment class. We need the views to be properly garbage collected when the activity\fragment is properly garbage collected.
  • Some examples of activity\fragment getting destroyed is when the user leaves the screen or rotates the device.
  • We have getter and setter added in the base presenter.
  • In the setter method, we wrap the view in the weak reference.
  • In the getter method, we extract the view from the weak reference.

Setting up the contract in view and presenter

class AllCreaturesActivity : AppCompatActivity(), AllCreaturesContract.View {

  private val presenter = AllCreaturesPresenter()

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(view)
    // Set the presenter here 
    presenter.setView(this)
  }

  override fun showCreaturesCleared() {
    // over-ridden methods of interface
  }
}
  • Implement the view part of the interface from the contract and override the methods
  • Have a presenter reference globally
  • Initialize the presenter in the onCreate() method and set the view using the presenter base class setter method
class AllCreaturesPresenter(): BasePresenter<AllCreaturesContract.View>(), AllCreaturesContract.Presenter {

  // over-ridden method - action from the view 
  override fun getAllCreatures(): LiveData<List<Creature>> {
    
  }

  // over-ridden method - action from the view 
  override fun clearAllCreatures() {
    // call-back to view 
    getView()?.showCreaturesCleared()
  }
}
  • Now we extend the presenter with base presenter
  • The base presenter has a generic parameter where we pass the interface for the view from the contract earlier we set up.
  • Then we implement interface from the contract where we add the interface and over-ride methods of it so we can get actions from the view.
  • To send the callback to the view back from the presenter, we can access the getter method of the base presenter and access the view and defined methods in the view so that overridden methods in view are triggered as the callback.

Communicating between presenter and repository

  • To communicate this way, define an interface with certain abstract methods as needed.
  • The repository implements the interface and the methods in the interface are over-ridden in repository.
  • Pass the repository object in the constructor in the presenter.
class AllCreaturesPresenter(private val repository: CreatureRepository = RoomRepository()) : BasePresenter<AllCreaturesContract.View>(), AllCreaturesContract.Presenter {

  override fun getAllCreatures(): LiveData<List<Creature>> { }

  override fun clearAllCreatures() { }
}
  • Using the repository reference call the overridden methods in the repository.
  • This enables unit testing since we can pass a mock repository during the testing instead of a real repository class.
  • But to send a callback again we can implement an interface in the presenter. Set the interface to the repository with the repository constructor and use it to delegate the callback if required.
  • We can have a contract similar to the one that exists between the presenter and view, here also.

Advantages

  • Clear separation of responsibilities between components. This separation allows for an easier understanding and maintenance of the codebase. Modularity allows you to e.g. switch to a different implementation of view component in order to completely change the application's UI, while all other components remain intact.
  • Easier testing. Since there are well-defined boundaries between components, it becomes much easier to test each component in isolation (by e.g. mocking other components).
  • Different people can work on different layers without depending on each other.
  • Low-cost Maintenance compared to MVC pattern, since fixing the bug and removing a component is easier.

Pitfals

  • Not every aspect of MVP is good because there are certain shortcomings or disadvantages in this type of pattern
  • At any moment of time, the activity gets destroyed and the reference to the view will be there in the presenter. The presenter might send a callback to the view attached to it but the view might have already been destroyed.
    • Earlier we could call a method when onDestry of view is called in the presenter and set the view to null, But now kotlin null saftey feature prevents it.
  • Presenter code reuse is not that easy, because most of the business logic exists in the presenter and the presenter code might become large, re-using the presenter in other places will have un-necessary businees logic attached to it which might not be of use in that perticular scenario. We can have many base presenters and segrigate functionalities but it will be a tedious and better architecture is availab le instead of this to handle in a better way.

Clone this wiki locally