Design Patterns

Android MVP Architecture for Beginners (Demo App)

Pinterest LinkedIn Tumblr

Android MVP Architecture addresses common difficulties such as maintainability and testability. So in this article, We will discuss the various difficulties that addressed by Android MVP Architecture

What is MVP ..?

MVP (Model View Presenter) design pattern is derived from pattern most of the things is same, where is the controller is replaced by the presenter. The MVP design pattern is set of guidelines that should follow for better code reusability and testability. As per MVP guidelines application is divided into three part Model, View Presenter.

Key point of MVP design pattern

  • MVP pattern the common difficulties in app, especially problem is related to maintainability and testability.
  • Model View Presenter increases the separation of the concern and facilitated unit
  • In the MVP pattern, we can separate background task from activity, fragment views to make them independent of most life cycle related event   
  • Using a consistent architectural and design pattern the development process become much more consistent, a lot of easier and more transparent
  • way the application becomes simpler, and overall application reliability increases remarkable. The application code becomes cleaner and more readable. Code maintainability better and most importantly becomes fun and developer are Happy

How MVP work ?

The MVP pattern is user interface software architecture pattern that reduces the behavior of UI components. In this case, it reduces the interaction with Activity and Fragments bare minimum by using Presenter. The Pressenter is simply a controller-like class to handle presentation logic and update view accordingly.  Using presenter we can write business logic in the presenter layer that have minimum interaction with views that make much easier to test and much faster to test. Why it makes faster to test, well simply put we no longer need to test input and output. the android content is gone so we no need of android emulator and device, We just need on JVM for running test which is quite fast.

Flow of MVP work ?

Just review the above figure that gives an explanation How MVP is work. Suppose they have shown some user information with a couple of variable like first and last name.  The user makes change in first and last name and save the information on by clicking on save button. In this state the presenter will be involved by obtaining first and last name, now presenter will check these are valid or not. If value invalid then presenter makes the error and show the error message on the view. However, the case value is correct the update the model and send callback to view showing updated value on the views.

The Robert C Martin say in A Handbook of Agile Software Craftsmanship – The one way to make deadline – the one way to go fast – Is the keep the code as clean as possible all times.

Layer of MVP architecture – Model,View and Presenter

Model

In the clean each layer own models and models contains data relevant that layer.

For example

  • View have own ViewModel to present data in the view      
  • Database Model may to retrieve database entity
  • Simliery Network model may be represent data entries to retrieve from server
  • When data move between layer – the Model is transformed from  one layer representation layer to another. 

Presenter

A Presenter is a  middle man between views and It business logic to the presentation of

The Presenter the retrive the data from model and format the data before passing the view . The Presenter also update the UI via View.

View

In MVP design pattern The Activity and Fragment and View is passive and can not access directly, The view a of the and propagate the event UI to the Presenter. For example onClick event lifecycle event etc, ,

The Views is exposed methods that control the presentation of data or model for instance show or hide certain elements.

A few important things keep in mind while using any design pattern.

As per clean architecture – One layer must depend on the layer below

  • View depends on presenter and presenter must be depended on use case
  • Code can not jump over the such as view can not access the use case  and presenter can not access the enity
  • The remarkable thing Dependency is followed downward see the flow diagram

MVP Sample App

Now we will a sample app following a MVP design pattern. Open Android Studio and create a new project. Enter project name like MVP example and select empty template and change activity name to LoginActivity .

Open activity_login.xml and add below code
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center"
    android:padding="@dimen/_8dp">


    <EditText
        android:id="@+id/loginActivity_firstName_editText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="First Name"
        tools:text="First Name" />

    <EditText
        android:id="@+id/loginActivity_lastName_editText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Last Name"
        android:layout_marginTop="@dimen/_8dp"
        tools:text="Last Name" />

    <Button
        android:id="@+id/loginActivity_login_button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/colorAccent"
        android:textColor="@color/colorWhite"
        android:layout_marginTop="@dimen/_16dp"
        android:text="Log in" />

</LinearLayout>
Add dimens value in dimens.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <dimen name="_16dp">16dp</dimen>
    <dimen name="_8dp">8dp</dimen>
    <dimen name="default_padding">4dp</dimen>
</resources>

Create a model class with named User.Java , which hold some user properties with getter setter such as id, first name, last name

package com.wave.mvpexample.data.model;

public class User {

    private int id;
    private String firstName;
    private String lastName;

    public User(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }


    public void setId(int id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }


    public String getLastName() {
        return lastName;
    }

}
For using MVP design pattern, Now I create login contract interface. Just create a new interface inside src folder and add below code
package com.wave.mvpexample.login;

import com.wave.mvpexample.data.model.User;

public interface LoginActivityMVP {

    interface View{

        String getFirstName();
        String getLastName();

        void showInputError();

        void setFirstName(String firstName);

        void setLastName(String lastName);

        void showUserSavedMessage();
    }

    interface Presenter {

        void setView(View view);

        void loginButtonClicked();

        void getCurrentUser();

    }

    interface Model {

        void createUser(String name, String lastName);

        User getUser();

    }
}

Create a model named is LoginModel

Just create a new class java which implements LoginActivityMVP.Model and add below code

package com.wave.mvpexample.data.model;

import com.wave.mvpexample.data.repo.LoginRepository;
import com.wave.mvpexample.login.LoginActivityMVP;

public class LoginModel implements LoginActivityMVP.Model {


    private LoginRepository repository;

    public LoginModel(LoginRepository repository) {
        this.repository = repository;
    }

    @Override
    public void createUser(String name, String lastName) {


        repository.saveUser(new User(name, lastName));


    }

    @Override
    public User getUser() {

        return repository.getUser();
    }
}
Create a Repository for playing model

In LoginModel you see we are using a LoginRepository. To create a new interface which provides user instance and save the user instance in the repository.

package com.wave.mvpexample.data.repo;

import com.wave.mvpexample.data.model.User;

public interface LoginRepository {

    User getUser();

    void saveUser(User user);
}
Let’s create a repository named is UserRepository

Now create a java file which implements LoginRepository and add block of code

package com.wave.mvpexample.data.repo;

import com.wave.mvpexample.data.model.User;

public class UserRepository implements LoginRepository {

    private User user;

    @Override
    public User getUser() {

        if (user == null) {
            User user = new User("Dinesh", "Kumar");
            user.setId(0);
            return user;
        } else {
            return user;
        }

    }

    @Override
    public void saveUser(User user) {

        if (user == null) {
            user = getUser();
        }

        this.user = user;

    }
}
Create Presenter for LoginActivity

As per MVP, we have created Model, View on above, Now we will create a presenter for LoginActivity, Just create a java class with named is LoginActivityPresenter which implementing LoginActivityMVP.Presenter. In presenter we are doing the following operation.

  • Pass the view instance in the presenter in setView(View mView) methods
  • Defining onClick() event in loginButtonClicked() methods
  • Body on getCurrentUser() methods is retruning user instance
package com.wave.mvpexample.login;

import android.support.annotation.Nullable;

import com.wave.mvpexample.data.model.User;

public class LoginActivityPresenter implements LoginActivityMVP.Presenter {

    @Nullable
    private LoginActivityMVP.View view;
    private LoginActivityMVP.Model model;

    public LoginActivityPresenter(LoginActivityMVP.Model model) {
        this.model = model;
    }

    @Override
    public void setView(LoginActivityMVP.View view) {

        this.view = view;

    }

    @Override
    public void loginButtonClicked() {

        if (view != null) {
            if (view.getFirstName().trim().equals("") || view.getLastName().trim().equals("")) {
                view.showInputError();
            } else {

                model.createUser(view.getFirstName(), view.getLastName());
                view.showUserSavedMessage();

            }

        }

    }

    @Override
    public void getCurrentUser() {

        User user = model.getUser();

        if (user != null) {
            if (view != null) {
                view.setFirstName(user.getFirstName());
                view.setLastName(user.getLastName());
            }
        }

    }
}

I have distributed project in different packages. So all project structure looks like below figure.

MVP Project Structure

Open LoginActivity and implements LoginActivityMVP.View

package com.wave.mvpexample.login;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import com.wave.mvpexample.R;
import com.wave.mvpexample.root.App;

import javax.inject.Inject;

public class LoginActivity extends AppCompatActivity implements LoginActivityMVP.View {

    @Inject
    LoginActivityMVP.Presenter presenter;


    private EditText firstName;
    private EditText lastName;
    private Button login;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);

        ((App) getApplication()).getComponent().inject(this);

        firstName = (EditText) findViewById(R.id.loginActivity_firstName_editText);
        lastName = (EditText) findViewById(R.id.loginActivity_lastName_editText);
        login = (Button) findViewById(R.id.loginActivity_login_button);

        login.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                presenter.loginButtonClicked();

            }
        });


    }

    @Override
    protected void onResume() {
        super.onResume();
        presenter.setView(this);
        presenter.getCurrentUser();
    }

    @Override
    public String getFirstName() {
        return firstName.getText().toString();
    }

    @Override
    public String getLastName() {
        return lastName.getText().toString();
    }

    @Override
    public void showInputError() {
        Toast.makeText(this, "First Name or last name cannot be empty", Toast.LENGTH_SHORT).show();

    }

    @Override
    public void setFirstName(String firstName) {
        this.firstName.setText(firstName);
    }

    @Override
    public void setLastName(String lastName) {
        this.lastName.setText(lastName);
    }

    @Override
    public void showUserSavedMessage() {
        Toast.makeText(this, "User saved successfully", Toast.LENGTH_SHORT).show();

    }
}

In this project, we are using Dagger2 with MVP. So don’t think much about Dagger2 read our another article with a complete explanation of Dagger2. In this project, I’m using a dagger without explanation. Just put as per given instruction

Create a LoginModule

Now create a java class named is LoginModule.java which annotated with @Module annotation

package com.wave.mvpexample.login;
import com.wave.mvpexample.data.model.LoginModel;
import com.wave.mvpexample.data.repo.LoginRepository;
import com.wave.mvpexample.data.repo.UserRepository;
import dagger.Module;
import dagger.Provides;
@Module
public class LoginModule {
    @Provides
    public LoginActivityMVP.Presenter provideLoginActivityPresenter(LoginActivityMVP.Model model){
        return new LoginActivityPresenter(model);
    }
    @Provides
    public LoginActivityMVP.Model provideLoginActivityModel(LoginRepository repository){
        return new LoginModel(repository);
    }
    @Provides
    public LoginRepository provideLoginRepository(){
        return new UserRepository();
    }
}

Furthermore create a AppModule

create a new package inside base package name is root. Now create a new class name AppModule in root package.

package com.wave.mvpexample.root;
import android.app.Application;
import android.content.Context;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@Module
public class AppModule {
    private Application application;
    public AppModule(Application application) {
        this.application = application;
    }
    @Provides
    @Singleton
    public Context provideContext() {
        return application;
    }
}

Now create AppComponent

Let’s create a interface name is AppComponent and add below code

package com.wave.mvpexample.root;
import com.wave.mvpexample.login.LoginActivity;
import com.wave.mvpexample.login.LoginModule;
import javax.inject.Singleton;
import dagger.Component;
@Singleton
@Component(modules = {AppModule.class, LoginModule.class})
public interface AppComponent {
    void inject(LoginActivity target);
}

After that create a Application

Now create class with extends Application add this code

package com.wave.mvpexample.root;
import android.app.Application;
import com.wave.mvpexample.login.LoginModule;
public class App extends Application {
    private AppComponent component;
    @Override
    public void onCreate() {
        super.onCreate();
        component = DaggerAppComponent.builder()
                .appModule(new AppModule(this))
                .loginModule(new LoginModule())
                .build();
    }
    public AppComponent getComponent() {
        return component;
    }
}

Add App in AndroidManifest

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.wave.mvpexample">
    <application
        android:name=".root.App"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme"
        tools:ignore="GoogleAppIndexingWarning">
        <activity android:name=".login.LoginActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

Finally, just rebuild the project, after rebuilding the DaggerAppComponent will be created. Then import the class and Run the project. This is the best way to using an Android MVP Architecture design pattern, activity will always clean and the code is maintainable and testable.

We have created a sample app with MVP Architect Android apps with Dagger2, Retrofit & RxJava,  RoomPersistence.    Must Read 

Download Sample Project- Android MVP Architecture

Write A Comment