Android, Architecture

From MVVM to VIP: Understanding Android Architecture Patterns

When it comes to developing mobile apps for Android, choosing the right architecture pattern is crucial for creating a scalable, maintainable, and easy-to-test app. There are several popular architecture patterns among developers, each with its own set of advantages and disadvantages. In this blog post, we’ll take a fun look at the differences between five of the most popular patterns: MVVM, MVP, MVI, VIP, and Redux.

MVVM (Model-View-ViewModel)

MVVM is a widely used architecture pattern in Android development. In this pattern, the model represents the data and business logic, the view is responsible for displaying the data, and the ViewModel acts as an intermediary between the view and the model. This pattern is known for its ability to facilitate testing and to separate concerns effectively.

class UserViewModel : ViewModel() {
    val userLiveData = MutableLiveData()
    fun getUser() {
        // fetch user data from model and update LiveData
        val user = userRepository.getUser()
        userLiveData.postValue(user)
    }
    fun updateUserPicture(pictureUri: Uri) {
        // update user's profile picture in model
        userRepository.updateUserPicture(pictureUri)
    }
}

In this example, UserViewModel acts as an intermediary between the view and the model to display user data on the screen. The ViewModel contains an instance of MutableLiveData that is updated when getUser() is called, which in turn calls the model’s getUser() method to retrieve the user data. Additionally, the ViewModel also provides a method updateUserPicture() to update the user’s profile picture in the model.

MVP (Model-View-Presenter)

MVP is another popular architecture pattern that is widely used in Android development. In this pattern, the view is responsible for displaying the data, the model represents the data and business logic, and the presenter acts as an intermediary between the view and the model. This pattern is known for its ability to facilitate testing and to separate concerns effectively.

class ToDoListPresenter(private val view: ToDoListView,
                        private val model: ToDoListModel) {
    
    fun loadTasks() {
        val tasks = model.getTasks()
        view.showTasks(tasks)
    }
    
    fun addTask(task: Task) {
        model.addTask(task)
        view.showTaskAdded()
    }
    
    fun completeTask(task: Task) {
        model.completeTask(task)
        view.showTaskCompleted()
    }
}

In this example, ToDoListPresenter acts as an intermediary between the view and the model to manage tasks in a to-do list app. The Presenter contains references to both the view and the model, and provides methods loadTasks(), addTask(), and completeTask() to handle user input and update the view with the latest data.

MVI (Model-View-Intent)

MVI is a relatively new architecture pattern that is gaining popularity in Android development. In this pattern, the model represents the data and business logic, the view is responsible for displaying the data, and the intent is used to handle user actions. This pattern is known for its ability to handle complex user interactions and to reduce the number of bugs.

sealed class SearchIntent {
    object LoadData : SearchIntent()
    data class SearchQuery(val query: String) : SearchIntent()
}
class SearchViewModel(private val searchUseCase: SearchUseCase) : ViewModel() {
    private val _searchViewState = MutableLiveData()
    val searchViewState: LiveData = _searchViewState
    fun processIntents(intents: Flow) {
        viewModelScope.launch {
            intents.collect { intent ->
                when (intent) {
                    is SearchIntent.LoadData -> {
                        val results = searchUseCase.getInitialData()
                        _searchViewState.value = SearchViewState.LoadedData(results)
                    }
                    is SearchIntent.SearchQuery -> {
                        val results = searchUseCase.search(intent.query)
                        _searchViewState.value = SearchViewState.SearchResults(results)
                    }
                }
            }
        }
    }
}

In this example, SearchViewModel uses the MVI pattern to handle user intents in a search functionality. The ViewModel contains a sealed class SearchIntent to represent different user intents, and a method processIntents() to collect and process these intents. The method uses a Flow to collect the intents, and a when statement to handle each intent and update the view state accordingly.

VIP (View-Interactor-Presenter)

VIP is an architecture pattern that is similar to MVP. In this pattern, the view is responsible for displaying the data, the presenter acts as an intermediary between the view and the interactor, and the interactor represents the business logic. This pattern is known for its ability to facilitate testing and to reduce the complexity of the code.

interface LoginView {
    fun showLoginSuccess()
    fun showLoginError()
}

interface LoginInteractor {
    fun login(username: String, password: String)
}

class LoginPresenter(private val view: LoginView,
                     private val interactor: LoginInteractor)
{
fun login(username: String, password: String) {
    interactor.login(username, password)
}

fun onLoginSuccess() {
    view.showLoginSuccess()
}

fun onLoginError() {
    view.showLoginError()
}

}

class LoginInteractorImpl(private val userRepository: UserRepository) : LoginInteractor {

override fun login(username: String, password: String) {
    val user = userRepository.getUser(username, password)
    if (user != null) {
        // login success
        presenter.onLoginSuccess()
    } else {
        // login error
        presenter.onLoginError()
    }
}
}

In this example, LoginPresenter acts as an intermediary between the view and the interactor (business logic) to handle user input in a login screen. The Presenter contains references to both the view and the interactor, and provides methods `login()`, `onLoginSuccess()`, and `onLoginError()` to handle user input and update the view with the latest data. The interactor `LoginInteractorImpl` takes care of the business logic of logging in a user, and communicates with the Presenter to update the view accordingly.

Redux

Redux is a state management architecture pattern that is widely used in Android development. In this pattern, the state of the app is kept in a single store, and all changes to the state are made through actions. This pattern is known for its ability to handle complex state management and to facilitate testing.

example in Part 2

MVC (Model-View-Controller)

MVC is an architecture pattern that has been widely used in Android development. In this pattern, the model represents the data and business logic, the view is responsible for displaying the data, and the controller acts as an intermediary between the view and the model. This pattern is known for its ability to facilitate testing and to separate concerns effectively.

example in Part 2

In conclusion, choosing the right architecture pattern is crucial for creating a scalable, maintainable, and easy-to-test Android app. The patterns we’ve discussed in this blog post offer different approaches to solving the same problem, and each has its own set of strengths and weaknesses. Whether you choose MVVM, MVP, MVI, VIP, Redux, or MVC, what’s important is that you choose an architecture pattern that works for your specific needs and helps you to create a great app.