paint-brush
The Evolution of MV* Patterns in Android: Part 1by@azamatnurkhojayev
248 reads

The Evolution of MV* Patterns in Android: Part 1

by Azamat NurkhojayevAugust 27th, 2023
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

In this article, we will analyze the evolution of presentation patterns such as MVC(Model-View-Controller), MVP(Model-View-Presenter), MVVM(Model-View-ViewModel), MVI(Model-View-Intent).
featured image - The Evolution of MV* Patterns in Android: Part 1
Azamat Nurkhojayev HackerNoon profile picture

In this article, we will analyze the evolution of presentation patterns such as MVC(Model-View-Controller), [MVP](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93presenter#:~:text=Model%E2%80%93view%E2%80%93presenter%20(MVP,mostly%20for%20building%20user%20interfaces.&text=In%20MVP%2C%20the%20presenter%20assumes,is%20pushed%20to%20the%20presenter.)(Model-View-Presenter), MVVM(Model-View-ViewModel), MVI(Model-View-Intent).


The problem of understanding the architectural approach in mobile development, in my opinion, lies in the abstractness of the architecture itself. Each developer has his own vision of how to correctly implement a particular pattern.

And what are representation patterns for?


  • structure the code and separate it into layers
  • write maintainable and testable code


Let's move on to what Model and View mean:


Model is the data layer. Usually, it can be a Repository and it talks to the database and the network.

View is the display level. On Android, this would be Activity, Fragment, or Custom View and should ideally be as dumb as possible.


MVC

This is the simplest pattern in Android, where the main business logic is contained in the Model class, and the View and Controller are implemented by the Activity and Fragment.


Below is a diagram and sample code:


Let's write our Repository:

interface BooksRepository {  
      
    fun fetchBooks(): List<Book>  
      
    fun saveInFavorites(id: Long): Boolean  
      
    fun removeFromFavorites(id: Long): Boolean  
      
}  
  
data class Book(val id: Long, val title: String, val author: String)


MVC release example:

class MainFragment : Fragment(R.layout.fragment_main) {  
  
    private lateinit var booksRepository: BooksRepository  
    private lateinit var booksRV: RecyclerView  
    private val adapter = BooksListAdapter()  
  
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {  
        super.onViewCreated(view, savedInstanceState)  
        booksRV = view.findViewById(R.id.books_rv)  
          
        booksRepository.fetchBooks {  
            adapter.submitList(it)  
        }  
        }  
  
    companion object {  
        @JvmStatic  
        fun newInstance() = MainFragment()  
    }  
}


Everything seems to be fine, but at first glance, over time, our MainFragment code will grow and it will be difficult to maintain it.


And here there are certain disadvantages:


  • difficult to write tests;
  • code becomes more difficult to maintain over time;
  • code is more difficult to read;


MVP

This approach allows you to create an abstraction of the view. To do this, you need to highlight the view interface with a specific set of properties and methods. The presenter, in turn, receives a reference to the implementation of the interface, subscribes to view events, and changes the model upon request.


Presenter - a layer between View and Model. The View passes the events to it, and the Presenter passes them on, if necessary, calls the Model, and returns the data to the View for rendering.



Presenter features:

  • Two-way communication with the presentation;
  • The View interacts directly with the Presenter by calling the appropriate functions or events of the Presenter instance;
  • The Presenter interacts with the View by using a special interface implemented by the View;
  • One Presenter instance is associated with one display;


Cons

  • Cyclic dependency between View and Presenter;
  • The presenter provides methods to handle the View lifecycle;
  • Bloated View interface;
  • Difficulty maintaining state;


MVP example.

Creating the MainView interface:

interface MainView {  
  
    fun setBooks(books: List<Book>)  
    fun showLoading()  
    fun hideLoading()  
    fun showError(message: String)  
  
}


We create the MainPresenter class and pass it into the MainView constructor:

class MainPresenter(private val view: MainView) {  
  
    private lateinit var booksRepository: BooksRepository  

	init {  
	    booksRepository = BooksRepositoryImpl()  
	}

    fun bind() {  
        view.showLoading()  
    }  
  
    fun fetchBooks() {  
        booksRepository.fetchBooks {  
            view.hideLoading()  
            view.setBooks(it)  
        }  
    }  
  
}


And finally MainFragment:

class MainFragment : Fragment(R.layout.fragment_main), MainView {  
  
    private lateinit var booksRV: RecyclerView  
    private lateinit var progressBar: ProgressBar  
    private val adapter = BooksListAdapter()  
    private lateinit var presenter: MainPresenter  
  
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {  
        super.onViewCreated(view, savedInstanceState)  
        booksRV = view.findViewById(R.id.books_rv)  
        progressBar = view.findViewById(R.id.progress_bar)  
  
        presenter = MainPresenter(this)  
        presenter.bind()  
        presenter.fetchBooks()  
    }  
  
    override fun setBooks(books: List<Book>) {  
        adapter.submitList(books)  
    }  
  
    override fun showLoading() {  
        progressBar.visibility = View.VISIBLE  
    }  
  
    override fun hideLoading() {  
        progressBar.visibility = View.GONE  
    }  
  
    override fun showError(message: String) {  
  
    }  
  
    companion object {  
        @JvmStatic  
        fun newInstance() = MainFragment()  
    }  
  
}


What's going on?

  • MainFragment, aka View, in the onViewCreated() method, creates a Presenter instance and passes itself to the constructor.
  • Presenter, when created, explicitly receives a View and creates an instance of the Repository
  • The Presenter accesses the Repository fetchBooks method.
  • Repository receives and gives data to Presenter.
  • Presenter calls View and passes the list of books