Android Core

MVP Architecture in Android — Complete Guide with Example

1. What is MVP Architecture?

MVP stands for Model–View–Presenter. It evolved from the classic MVC pattern to solve Android’s problem of tight coupling between the View and Controller. In MVP, the Presenter acts as the middle layer between the View and Model, making the architecture more testable, modular, and maintainable.

2. Key Components of MVP

  • Model: Manages data and business logic. It can fetch data from a database, network, or API, and expose methods to the Presenter.
  • View: Handles UI elements and user interactions. It’s passive — meaning it only displays data and sends user actions to the Presenter.
  • Presenter: Connects the View and the Model. It retrieves data from the Model and updates the View accordingly. It contains no Android framework code, which makes it easy to test.

3. MVP Data Flow

  1. User interacts with the View (e.g., clicks a button).
  2. The View delegates the event to the Presenter.
  3. The Presenter requests data or performs logic via the Model.
  4. The Model returns data to the Presenter.
  5. The Presenter updates the View with the new data.

4. Advantages of MVP

  • Improved testability since the Presenter doesn’t depend on Android SDK.
  • Better separation of concerns than MVC.
  • Reusability — Presenter can be reused with different Views.
  • Clean and maintainable architecture for medium to large projects.

5. Example: Simple Login Feature Using MVP

Below is an example implementation of MVP pattern for a login screen.

📁 Folder Structure


                    ├── model/
                    │   └── UserModel.kt
                    ├── presenter/
                    │   └── LoginPresenter.kt
                    ├── view/
                    │   ├── LoginView.kt
                    │   └── LoginActivity.kt
                    └── activity_login.xml
                    

🎨 View: activity_login.xml


                    <LinearLayout
                        xmlns:android="http://schemas.android.com/apk/res/android"
                        android:layout_width="match_parent"
                        android:layout_height="match_parent"
                        android:orientation="vertical"
                        android:padding="24dp">

                        <EditText
                            android:id="@+id/etUsername"
                            android:hint="Username"
                            android:layout_width="match_parent"
                            android:layout_height="wrap_content"/>

                        <EditText
                            android:id="@+id/etPassword"
                            android:hint="Password"
                            android:inputType="textPassword"
                            android:layout_width="match_parent"
                            android:layout_height="wrap_content"/>

                        <Button
                            android:id="@+id/btnLogin"
                            android:text="Login"
                            android:layout_width="match_parent"
                            android:layout_height="wrap_content"/>
                    </LinearLayout>
                    

🧩 Model: UserModel.kt


                    package com.example.mvp.model

                    class UserModel {
                        fun isValidUser(username: String, password: String): Boolean {
                            return username.isNotEmpty() && password.length >= 6
                        }
                    }
                    

🧠 Presenter: LoginPresenter.kt


                    package com.example.mvp.presenter

                    import com.example.mvp.model.UserModel
                    import com.example.mvp.view.LoginView

                    class LoginPresenter(private val view: LoginView) {

                        private val userModel = UserModel()

                        fun onLoginClicked(username: String, password: String) {
                            if (username.isEmpty() || password.isEmpty()) {
                                view.showError("Fields cannot be empty")
                                return
                            }

                            if (userModel.isValidUser(username, password)) {
                                view.showSuccess("Login successful!")
                            } else {
                                view.showError("Invalid username or password")
                            }
                        }
                    }
                    

🖥️ View Interface: LoginView.kt


                    package com.example.mvp.view

                    interface LoginView {
                        fun showSuccess(message: String)
                        fun showError(message: String)
                    }
                    

🎬 Activity: LoginActivity.kt


                    package com.example.mvp.view

                    import android.os.Bundle
                    import android.widget.Toast
                    import androidx.appcompat.app.AppCompatActivity
                    import com.example.mvp.R
                    import com.example.mvp.presenter.LoginPresenter
                    import kotlinx.android.synthetic.main.activity_login.*

                    class LoginActivity : AppCompatActivity(), LoginView {

                        private lateinit var presenter: LoginPresenter

                        override fun onCreate(savedInstanceState: Bundle?) {
                            super.onCreate(savedInstanceState)
                            setContentView(R.layout.activity_login)

                            presenter = LoginPresenter(this)

                            btnLogin.setOnClickListener {
                                val username = etUsername.text.toString()
                                val password = etPassword.text.toString()
                                presenter.onLoginClicked(username, password)
                            }
                        }

                        override fun showSuccess(message: String) {
                            Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
                        }

                        override fun showError(message: String) {
                            Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
                        }
                    }
                    

6. Disadvantages of MVP

  • Can lead to a large number of interfaces and boilerplate code.
  • Presenter can grow too big if not managed properly (called “Massive Presenter”).
  • Still lacks lifecycle awareness compared to MVVM.

Conclusion

The MVP pattern provides a clean and testable structure for Android apps. It improves upon MVC by decoupling business logic from UI logic, allowing for easier unit testing and modular development. However, in modern Android, developers often move towards MVVM and Clean Architecture using Jetpack components for even better lifecycle handling.