ANDROID: MVP ARCHITECTURE 101.
Hello, Android Developer!
I’d like to tell you a few things about Model-View-Presenter (MVP) architecture pattern. Some refer to it as a design pattern but I’ll preferably call it an architecture pattern because it deals with high-level organization of your code and also, the separation of functionalities of your code into different concerns which aids re-usability amongst other peculiar functions.
Because this is an introductory course, I wouldn’t bore you with so much lengthy definitions and seemingly verbose codes but I will get you up and running with MVP. I’ll be giving brief and concise definition for the sake of simplicity and also for the scope of this introductory course.
Model : It is responsible for managing your data.
View: All user interactions are done in your View class.
Presenter: Your application logic is stored in your Presenter class.
Short and Precise, Isn’t it? Told you so!
Like I said earlier, I’m not here to bore you with unnecessary knowledge.
Looking at the diagram above (look at the arrows ), you’d notice three things
- There is NO RELATIONSHIP BETWEEN MODEL AND VIEW.
- There is a SINGLE RELATIONSHIP BETWEEN MODEL AND PRESENTER.
- There is a DUAL RELATIONSHIP BETWEEN PRESENTER AND VIEW
DEDUCTIONS.
- The model is not aware of the view and also the view is not aware that the model exist.
- The presenter is aware that the model exists but the model is not aware of the presenter.
- The presenter is aware of the view and the view is also aware of the presenter.
Now I’m going to show you how to implement this in a sample Verify Password Android Application.
STEPS INVOLVED IN APPLICATION
Create PROJECT: Verify Password.
- Select Empty Activity.
- Tick “Generate Layout File”
- Tick “Backward Compatibility (AppCompat)” then finish.
Update activity_main layout file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include layout="@layout/toolbar" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/def_margin_left"
android:layout_marginTop="@dimen/def_margin_top"
android:layout_marginRight="@dimen/def_margin_left"
android:layout_marginBottom="@dimen/margin_bottom"
android:text="@string/create_pin"
android:textColor="@android:color/black"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/def_margin_left"
android:layout_marginRight="@dimen/def_margin_left"
android:layout_marginBottom="@dimen/def_margin_bottom"
android:text="@string/four_digit_pin"
android:textColor="@android:color/black"
android:textSize="12sp" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/def_margin_left"
android:layout_marginRight="@dimen/def_margin_left"
android:layout_marginBottom="@dimen/margin_bottom"
android:text="@string/newpin"
android:textColor="#A5A5A5"
android:textSize="18sp"
android:textStyle="bold" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/def_margin_left"
android:layout_marginRight="@dimen/def_margin_left"
android:gravity="center_vertical"
>
<EditText
android:id="@+id/newpin_one"
android:layout_width="40dp"
android:layout_height="wrap_content"
android:editable="false"
android:gravity="center_horizontal"
android:hint="0"
android:imeOptions="actionNext"
android:inputType="phone"
android:maxLength="1"
android:singleLine="true" />
<EditText
android:id="@+id/newpin_two"
android:layout_width="40dp"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/margin_bottom"
android:editable="false"
android:gravity="center_horizontal"
android:hint="0"
android:imeOptions="actionNext"
android:inputType="phone"
android:maxLength="1"
android:singleLine="true" />
<EditText
android:id="@+id/newpin_three"
android:layout_width="40dp"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/margin_bottom"
android:editable="false"
android:gravity="center_horizontal"
android:hint="0"
android:imeOptions="actionNext"
android:inputType="phone"
android:maxLength="1"
android:singleLine="true" />
<EditText
android:id="@+id/newpin_four"
android:layout_width="@dimen/def_layout_width"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/margin_bottom"
android:editable="false"
android:gravity="center_horizontal"
android:hint="0"
android:imeOptions="actionNext"
android:inputType="phone"
android:maxLength="1"
android:singleLine="true" />
<ImageView
android:id="@+id/positivechecked1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/margin_bottom"
android:src="@drawable/positivechecked"
android:visibility="invisible" />
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/def_margin_left"
android:layout_marginTop="18dp"
android:layout_marginRight="@dimen/def_margin_left"
android:layout_marginBottom="@dimen/margin_bottom"
android:text="Confirm PIN"
android:textColor="#A5A5A5"
android:textSize="18sp"
android:textStyle="bold" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/def_margin_left"
android:layout_marginRight="@dimen/def_margin_left"
android:gravity="center_vertical"
>
<EditText
android:id="@+id/confirmpin_one"
android:layout_width="@dimen/def_layout_width"
android:layout_height="wrap_content"
android:editable="false"
android:gravity="center_horizontal"
android:hint="0"
android:imeOptions="actionNext"
android:inputType="phone"
android:maxLength="1"
android:singleLine="true" />
<EditText
android:id="@+id/confirmpin_two"
android:layout_width="40dp"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/margin_bottom"
android:editable="false"
android:gravity="center_horizontal"
android:hint="0"
android:imeOptions="actionNext"
android:inputType="phone"
android:maxLength="1"
android:singleLine="true" />
<EditText
android:id="@+id/confirmpin_three"
android:layout_width="40dp"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/margin_bottom"
android:editable="false"
android:gravity="center_horizontal"
android:hint="0"
android:imeOptions="actionNext"
android:inputType="phone"
android:maxLength="1"
android:singleLine="true" />
<EditText
android:id="@+id/confirmpin_four"
android:layout_width="40dp"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/margin_bottom"
android:editable="false"
android:gravity="center_horizontal"
android:hint="0"
android:imeOptions="actionNext"
android:inputType="phone"
android:maxLength="1"
android:singleLine="true" />
<ImageView
android:id="@+id/positivechecked2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/margin_bottom"
android:src="@drawable/positivechecked"
android:visibility="invisible" />
</LinearLayout>
<Button
android:id="@+id/btn_choose_pin"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/def_margin_left"
android:layout_marginTop="@dimen/def_margin"
android:layout_marginRight="@dimen/def_margin_left"
android:background="@drawable/btn_red"
android:text="@string/finish"
android:textAllCaps="false"
android:textColor="@color/white" />
</LinearLayout>
You can simply copy and paste that .
I forgot to tell you something earlier. You will be needing an interface for your View class and Presenter always.
Hey, why use an Interface?
What you really need to know for now is that interface helps to create a single point of reference. A single point of reference allows you to know all the behaviors supported by view to be implemented by your presenter. Like a proper structure of your codebase.
There have been series of debate why we shouldn’t use an Interface in a Presenter. I always love using an interface in my presenter haha, but then we’ll discuss this in the second release of this article.
Too much talk already. Shall we create the interface now?
public interface AppPinContract {
interface View {
void showButtonClick(boolean b);
void setButtonColor(int color);
void navigateNextScreen();
void showError(String error);
void showTickVisibility(int value);
}
interface Presenter {
void loadNextScreen();
void defaultSettings();
void verifyEntries();
void savePassword(String password);
String appendIndvidualPassword(String first, String second, String third, String fourth);
}
}
How did I come up with these method names?
Oh please, it’s no big deal actually. Remember user interactions like show button click, set button colour are all done in the View. But then, there is always a defacto naming convention. Presenters always talk about the action to be carried out like verifyEntries(), deletePassword() etc. Views on the other hand mostly begin with: enable, disable, show, hide etc. because it deals with what the user can see.
Next step: The Model class
public class AppPinModel {
private String password;
public AppPinModel(String password) {
this.password = password;
}
public AppPinModel() {
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
Model manages application data, like a POJO class. It stores data with the help of getters and setters. And the only data we have in our application is the password the user enters.
Next Step: Presenter Class
public class AppPinPresenter implements AppPinContract.Presenter {
private AppPinContract.View view;
private AppPinModel model;
public AppPinPresenter(AppPinContract.View view) {
this.view = view;
model = new AppPinModel();
}
@Override
public void loadNextScreen() {
view.navigateNextScreen();
}
@Override
public void defaultSettings() {
view.setButtonColor(R.drawable.btn_ash);
view.showButtonClick(false);
view.showTickVisibility(View.INVISIBLE);
}
@Override
public void verifyEntries() {
view.setButtonColor(R.drawable.btn_red);
view.showButtonClick(true);
view.showTickVisibility(View.VISIBLE);
}
@Override
public void savePassword(String password) {
model.setPassword(password);
}
@Override
public String appendIndvidualPassword(String first, String second, String third, String fourth) {
StringBuilder sb = new StringBuilder();
sb.append(first).append(second).append(third).append(fourth);
return sb.toString();
}
}
Here’s my presenter class. It implements the interface right?
An instance of an my Model is created because it will be used here. Remember the diagram above and my deduction: “ The presenter is aware that the model exists but the model is not aware of the presenter. ”
The view instance is passed into the constructor of the Presenter class.
The codes are pretty explanatory, I think.
Let’s also implement the View interface in the View class
Remember, your View class can be an Activity, a View or a Fragment. Here it’s an activity
FINAL STEP: VIEW CLASS
public class MainActivity extends AppCompatActivity implements AppPinContract.View {
private Button mButton;
private AppPinContract.Presenter presenter;
private EditText pin1, pin2, pin3, pin4, con_pin1, con_pin2, con_pin3, con_pin4;
private String password1, password2;
ImageView positive1, positive2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
setTitle("");
init();
presenter = new AppPinPresenter(this);
presenter.defaultSettings();
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
presenter.loadNextScreen();
}
});
}
private void init() {
mButton = findViewById(R.id.btn_choose_pin);
pin1 = findViewById(R.id.newpin_one);
pin2 = findViewById(R.id.newpin_two);
pin3 = findViewById(R.id.newpin_three);
pin4 = findViewById(R.id.newpin_four);
con_pin1 = findViewById(R.id.confirmpin_one);
con_pin2 = findViewById(R.id.confirmpin_two);
con_pin3 = findViewById(R.id.confirmpin_three);
con_pin4 = findViewById(R.id.confirmpin_four);
pin1.addTextChangedListener(watcher);
pin2.addTextChangedListener(watcher);
pin3.addTextChangedListener(watcher);
pin4.addTextChangedListener(watcher);
con_pin1.addTextChangedListener(watcher);
con_pin2.addTextChangedListener(watcher);
con_pin3.addTextChangedListener(watcher);
con_pin4.addTextChangedListener(watcher);
positive1 = findViewById(R.id.positivechecked1);
positive2 = findViewById(R.id.positivechecked2);
}
@Override
public void showButtonClick(boolean b) {
mButton.setEnabled(b);
}
@Override
public void setButtonColor(int color) {
mButton.setBackground(getResources().getDrawable(color));
}
@Override
public void navigateNextScreen() {
Toast.makeText(this, "Your intent goes here", Toast.LENGTH_SHORT).show();
}
@Override
public void showError(String error) {
}
@Override
public void showTickVisibility(int value) {
positive1.setVisibility(value);
positive2.setVisibility(value);
}
TextWatcher watcher = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
EditText editText = (EditText) getCurrentFocus();
if (editText != null && editText.length() > 0) {
View next = editText.focusSearch(View.FOCUS_RIGHT);
if (next != null) {
next.requestFocus();
}
}
password1 = presenter.appendIndvidualPassword( pin1.getText().toString(),pin2.getText().toString(),pin3.getText().toString(),pin4.getText().toString());
password2 = presenter.appendIndvidualPassword(con_pin1.getText().toString(),con_pin2.getText().toString(),con_pin3.getText().toString(),con_pin4.getText().toString());
if (password1.equals(password2)) {
presenter.verifyEntries();
presenter.savePassword(password1);
return;
}
if (!(password1.equals(password2))) {
presenter.defaultSettings();
return;
}
}
@Override
public void afterTextChanged(Editable editable) {
}
};
@Override
public boolean onSupportNavigateUp() {
onBackPressed();
return true;
}
}
Cool isn’t it?
The view class implements the interface and the methods are overriden.
An Object of the presenter is created and the constructor points to the view context, then you can hustle your way through the codes.
This is like the most pretty basic MVP implementation. If you really understand this then you understanding the basics of MVP.
The part 2 of this course will dwell on single responsibility principle, using parcelable and serializable, types of MVP and also, writing your custom MVP architecture pattern. Till then, I need your sincere feedback on this one. Thank you!
Full source code available on github: https://github.com/themavencoder/VerifyPassword
Screenshots:
Thanks for your detailed explanation. When will get Part 2 of this course?
Thanks for your kind response. Part 2 will be available soon.