MVP architecture in Android

Jintin
3 min readNov 12, 2017

--

Android development is as freedom as its ecosystem. Though you can choose what architecture you want to use to built your Apps, the simple way is use MVC. Here is a simple example display a page where user can update his name:

// Model
public class User {
public String name;
}
// View
<?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"
>
<EditText
android:id="@+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
<Button
android:id="@+id/update"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/ab_apply"
/>
</LinearLayout>
// Controller
public class EditActivity extends Activity {
public static final String EXTRA_USER = "user";
@Override
protected void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

EditText nameText = findViewById(R.id.name);
Button update = findViewById(R.id.update);
User user = getIntent().getParcelableExtra(EXTRA_USER);
nameText.setText(user.name);
update.setOnClickListener(view -> {
String name = nameText.getText().toString();
if (!TextUtils.isEmpty(name)) {
user.name = name;
setResult(RESULT_OK, new Intent().putExtra(EXTRA_USER, user));
finish();
}
});
}
}

Model and View is so simple so you probably aware that we’re about talking Activity(Controller) which takes too much responsibility. Xml View is too dumb so Activity has to do lots view setup or update. So how MVP can help us?

Model-View-Presenter

Since Activity take lots View’s responsibility, we can put them as same View layer if we extract the business logic into another new layer, Presenter. The original Model and View will remain identical. Here is the split version of the original EditActivity.

// View
public class EditActivity extends Activity {

public static final String EXTRA_USER = "user";
private EditPresenter presenter;
EditText nameText;

@Override
protected void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
nameText = findViewById(R.id.name);
Button submitButton = findViewById(R.id.update);
presenter = new EditPresenter(this); User user = getIntent().getParcelableExtra(EXTRA_USER);
presenter.setUser(user);
submitButton.setOnClickListener(view ->
presenter.updateUser(nameText.getText().toString()));
}

public void updateName(final String name) {
nameText.setText(name);
}

public void onSubmit(final User user) {
setResult(RESULT_OK, new Intent().putExtra(EXTRA_USER, user));
finish();
}
}
// Presenter
public class EditPresenter {

private final EditActivity view;
private User user;

public EditPresenter(final EditActivity view) {
this.view = view;
}

public void setUser(final User user) {
this.user = user;
view.updateName(user.name);
}

public void updateUser(final String name) {
if (!TextUtils.isEmpty(name)){
user.name = name;
view.onSubmit(user);
}
}
}

Looks much better. And we can provide an Contract interface for both side to decouple dependency of the real Class.

public interface EditContract {

public interface View {
void updateName(String name);

void onSubmit(User user);
}

public interface Presenter {

void setUser(User user);

void updateUser(String name);
}
}

So the Presenter will not rely on the EditActity but the EditContract interface. The final version of originEditActivity will split into this three component.

// Contract
public interface EditContract {
public interface Presenter {
void setUser(User user);

void updateUser(String name);
}
public interface View {
void updateName(String name);

void onSubmit(User user);
}
}
// View
public class EditActivity extends Activity implements EditContract.View {

public static final String EXTRA_USER = "user";
private EditContract.Presenter presenter;
EditText nameText;

@Override
protected void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
nameText = findViewById(R.id.name);
Button submitButton = findViewById(R.id.update);
presenter = new EditPresenter(this);
User user = getIntent().getParcelableExtra(EXTRA_USER);
presenter.setUser(user);
submitButton.setOnClickListener(view ->
presenter.updateUser(nameText.getText().toString()));
}

@Override
public void updateName(final String name) {
nameText.setText(name);
}

@Override
public void onSubmit(final User user) {
setResult(RESULT_OK, new Intent().putExtra(EXTRA_USER, user));
finish();
}
}
// Presenter
public class EditPresenter implements EditContract.Presenter {

private final EditContract.View view;
private User user;

public EditPresenter(final EditContract.View view) {
this.view = view;
}

@Override
public void setUser(final User user) {
this.user = user;
view.updateName(user.name);
}

@Override
public void updateUser(final String name) {
if (!TextUtils.isEmpty(name)){
user.name = name;
view.onSubmit(user);
}
}
}

After this modification, we separate the business logic and the View. The presenter is pure business logic and platform independent. The View now is pure View’s setup and update. Most important thing, every module is now TESTABLE with each simple function responsibility.

What’s Next

  • EditActivity is not really decouple with EditPresenter in our example since the initialization is made inside EditActivity , how can we improve?
  • What should we do if there is a much complex View with multiple layer, ex: Activity contains Fragment which contains ListView using Adapter.
  • Will Presenter leak Activity?

Reference

--

--

Jintin

Android/iOS developer, husband and dad. Love to build interesting things to make life easier.