MVP and modularization, exploring Android programming.
This article is contributed by Ben Ben, a thoughtful veteran. A very insightful veteran.
Ben’s blog address:
http://www.jianshu.com/p/fb057953131e
0. Background
The first time I encountered the MVP design pattern was in 2014 when I read “Building High-Quality Android Applications: 50 Essential Tips for Android Development”. Since then, I have gradually tried to use it, and so far, it is not a model that can be perfectly applied to every scenario; it must be used correctly to maximize its effectiveness.
1. Overview of Functions
-
Layering: Separating code into layers, extracting data, models, and interfaces.
-
Reuse: Multiple implementations of the V layer or P layer interfaces.
2. Function – Layering
One reason we are fascinated by MVP is that when we were writing complex business Activities early on, the amount of code became too large, leading to poor readability. MVP effectively reduces the amount of code in Activities through three layers of separation. In understanding this function, I believe that it is suitable to use the MVP pattern only when the code volume is relatively large (greater than 1000 lines) and the various functional modules within the Activity are highly coupled.
3. Function – Reuse
This is another very elegant usage scenario for MVP.
-
When multiple layout interfaces need to be implemented, but the business logic is different (i.e., one V layer corresponds to multiple P layers), MVP is very suitable.
-
Of course, it is also applicable when multiple layouts have inconsistent architectures, but the business logic is consistent (i.e., one P layer corresponds to multiple V layers), but I have not encountered this situation yet.
Here’s an example: The requirement is to implement multiple interfaces below, with consistent layout architecture, but different data content and trigger logic.
①. V Layer
package com.benhero.design.mvp.view;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import com.benhero.design.R;
import com.benhero.design.mvp.bean.MvpItem;
import com.benhero.design.mvp.presenter.MvpPresenterD;
import com.benhero.design.mvp.presenter.MvpContract;
import com.benhero.design.mvp.presenter.MvpPresenterB;
import com.benhero.design.mvp.presenter.MvpPresenterC;
import com.benhero.design.mvp.presenter.MvpPresenterA;
import java.util.ArrayList;
import java.util.List;
/**
* MVP
*
* @author benhero
*/
public class MvpActivity extends AppCompatActivity implements MvpContract.View, View.OnClickListener {
public static final String EXTRA_ENTER = "enter";
/**
* 1 : A
*/
public static final int ENTER_A = 1;
/**
* 2 : B
*/
public static final int ENTER_B = 2;
/**
* 3 : C
*/
public static final int ENTER_C = 3;
/**
* 4 : D
*/
public static final int ENTER_D = 4;
private MvpContract.Presenter mPresenter;
private TextView mUpgradeBtn;
private ListView mListView;
private List mList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mvp_layout);
initView();
checkIntent();
mListView.setAdapter(new MVPAdapter());
}
private void initView() {
mUpgradeBtn = (TextView) findViewById(R.id.mvp_btn);
mUpgradeBtn.setOnClickListener(this);
mListView = (ListView) findViewById(R.id.mvp_listview);
}
private void checkIntent() {
Intent intent = getIntent();
if (intent != null) {
int enter = intent.getIntExtra(EXTRA_ENTER, 0);
if (enter == 0) {
errorEnter();
} else {
initData(enter);
}
} else {
errorEnter();
}
}
/*
* Status error
*/
private void errorEnter() {
Toast.makeText(this, "Error Intent", Toast.LENGTH_SHORT).show();
finish();
}
private void initData(int extra) {
switch (extra) {
case ENTER_A:
mPresenter = new MvpPresenterA(this);
break;
case ENTER_B:
mPresenter = new MvpPresenterB(this);
break;
case ENTER_C:
mPresenter = new MvpPresenterC(this);
break;
case ENTER_D:
mPresenter = new MvpPresenterD(this);
break;
default:
errorEnter();
break;
} if (mPresenter != null) {
mPresenter.initData();
}
}
@Override
protected void onResume() {
super.onResume();
if (mPresenter != null) {
mPresenter.onResume();
}
}
@Override
public void onClick(View v) {
if (v.equals(mUpgradeBtn)) {
Intent intent = new Intent(this, MvpResultActivity.class);
intent.putExtra(MvpResultActivity.EXTRA_ENTER,mPresenter != null ? mPresenter.getEnter() :
MvpResultActivity.ENTER_MAIN);
this.startActivity(intent);
}
}
@Override
public void initData(List list) {
mList.clear();
mList.addAll(list);
}
@Override
public void setTitleText(int id) {
setTitle(getString(id));
}
@Override
public void setUpgradeBtnText(int id) {
mUpgradeBtn.setText(id);
}
@Override
public void setPresenter(MvpContract.Presenter presenter) {
mPresenter = presenter;
}
/**
* List adapter
*
* @author benhero
*/
private class MVPAdapter extends BaseAdapter {
@Override
public int getCount() {
return mList.size();
}
@Override
public Object getItem(int position) {
return mList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
MyViewHolder holder;
if (convertView == null) {
holder = new MyViewHolder();
convertView = LayoutInflater.from(MvpActivity.this).inflate(R.layout.mvp_list_item, parent, false);
holder.mIndex = (TextView) convertView.findViewById(R.id.mvp_index);
holder.mTitle = (TextView) convertView.findViewById(R.id.mvp_title);
holder.mDesc = (TextView) convertView.findViewById(R.id.mvp_desc);
holder.mDivider = convertView.findViewById(R.id.mvp_divider);
convertView.setTag(holder);
} else {
holder = (MyViewHolder) convertView.getTag();
}
MvpItem itemBean = mList.get(position);
holder.mIndex.setText(position + 1 + "");
holder.mTitle.setText(itemBean.getTitleId());
holder.mDesc.setText(itemBean.getDescId());
holder.mDivider.setVisibility(position == mList.size() - 1 ? View.GONE : View.VISIBLE);
return convertView;
}
/**
* ViewHolder
*/
class MyViewHolder {
TextView mIndex;
TextView mTitle;
TextView mDesc;
View mDivider;
}
}
}
Above is our processing of the V layer, choosing different MvpPresenters to handle different interface data and interaction logic based on different intent data.
②. P Layer
Below is a code example of a certain P layer.
package com.benhero.design.mvp.presenter;
import com.benhero.design.R;
import com.benhero.design.mvp.bean.MvpItem;
import com.benhero.design.mvp.view.MvpResultActivity;
import java.util.ArrayList;
import java.util.List;
/**
* MvpPresenterA
*
* @author benhero
*/
public class MvpPresenterA implements MvpContract.Presenter {
private final MvpContract.View mView;
public MvpPresenterA(MvpContract.View view) {
mView = view;
}
@Override
public void start() {
}
@Override
public void initData() {
List list = new ArrayList<>();
list.add(createFactor(R.string.mvp_a_factor_title_1, R.string.mvp_a_factor_desc_1));
list.add(createFactor(R.string.mvp_a_factor_title_2, R.string.mvp_a_factor_desc_2));
mView.initData(list);
mView.setTitleText(R.string.mvp_a_title);
mView.setUpgradeBtnText(R.string.mvp_a_upgrade_btn);
}
private MvpItem createFactor(int titleId, int descId) {
MvpItem item = new MvpItem();
item.setTitleId(titleId);
item.setDescId(descId);
return item;
}
@Override
public int getEnter() {
return MvpResultActivity.ENTER_A;
}
@Override
public void onResume() {
}
}
③. V Layer and P Layer Interfaces
The interface classes for V and P refer to the design in Google’s MVP architecture open-source project. Specifically, the interface class in this article is as follows:
package com.benhero.design.mvp.presenter;
import com.benhero.design.mvp.base.BasePresenter;
import com.benhero.design.mvp.base.BaseView;
import com.benhero.design.mvp.bean.MvpItem;
import java.util.List;
/**
* MVP Interface
*
* @author benhero
*/
public interface MvpContract {
/**
* MVP Logic Control Interface
*/
interface Presenter extends BasePresenter {
void initData();
int getEnter();
void onResume();
}
/**
* MVP View Interface
*/
interface View extends BaseView {
void initData(List list);
void setTitleText(int id);
void setUpgradeBtnText(int id);
}
}
4. Disadvantages
The biggest disadvantage of MVP is readability. When there are too many mutual calls between the M layer and V layer, during debugging or reading the code, one needs to keep jumping back and forth between the two. If MVP is not used and the code is well-ordered, it can be read smoothly from top to bottom. Another significant factor affecting readability is interfaces! When you encounter a P call while reading the V layer, you click to jump, and first jump to the interface class, then click to jump to the implementation, which is cumbersome (of course, you can also directly jump to the implementation method using shortcuts).
5. Suggestions
If the M layer or V layer has no possibility of reuse, directly discard the interface! Interfaces are designed to regulate the behavior of classes for reuse and polymorphism. For certain business developments, there is no possibility of reuse; you can boldly discard it. Another role of interfaces is to constrain the access range of visitors, deciding whether to use them based on the situation. For scenarios with reuse, interfaces are certainly indispensable.
II. Modularization
In our development process, we often encounter scenarios where the Activity interface can be divided into multiple modules, and there is little interaction between each module. At this point, we can adopt a modularization approach to solve the problem of excessive Activity code.
1. Approach
In fact, Google has provided a solution for implementing such requirements: Fragment. An Activity can be divided into multiple Fragments, and views can be combined for different screens, which is very useful. Fragments will handle the Activity-related lifecycle very well, which is great.
Note: If an Activity only wraps a Fragment and has no other views, it is meaningless! I have done many silly things like this in my youth. In such a scenario, it is better to have a single Activity directly.
2. New Concept
Here, we need to introduce a new concept: ViewHolder (you can also name it Presenter or Module, etc.). The role: encapsulating the business logic related to the interface, lightweight. The basic class is as follows, which can be adjusted according to your needs.
package com.benhero.design.module.base;
import android.view.View;
/**
* ViewHolder Base Class
*
* @author benhero
*/
public class ViewHolder {
private View mContentView;
public ViewHolder() {
}
public ViewHolder(View contentView) {
mContentView = contentView;
}
public final void setContentView(View contentView) {
mContentView = contentView;
}
public View getContentView() {
return mContentView;
}
}
3. Case
The following is a clear interface for view modularization. The image shows the effect of the drawer sliding up to become image 2, using the BottomSheet component.
Next, we will demonstrate how to modularize a relatively complex Activity layer by layer from Activity→Fragment→ViewHolder.
1. Activity
package com.benhero.design.module.activity;
import android.os.Bundle;
import android.support.design.widget.BottomSheetBehavior;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import com.benhero.design.R;
/**
* Modularized Activity
*
* @author benhero
*/
public class ModuleActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_module);
BottomSheetBehavior behavior = BottomSheetBehavior.from(findViewById(R.id.activity_main_bottom_sheet));
behavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
}
}
<?xml version="1.0" encoding="utf-8"?><RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#282828"
tools:context="com.benhero.design.module.activity.ModuleActivity">
<fragment
android:id="@+id/activity_module_bg_fragment"
android:name="com.benhero.design.module.bg.ModuleBgFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:layout="@layout/fragment_module_bg"/>
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipChildren="false"
android:clipToPadding="false">
<RelativeLayout
android:id="@+id/activity_main_bottom_sheet"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/common_margin"
android:layout_marginRight="@dimen/common_margin"
android:clipChildren="false"
android:clipToPadding="false"
app:behavior_hideable="true"
app:behavior_peekHeight="@dimen/main_bottom_sheet_peek_height"
app:elevation="40dp"
app:layout_behavior="android.support.design.widget.BottomSheetBehavior">
<fragment
android:id="@+id/activity_main_bottom_sheet_fragment"
android:name="com.benhero.design.module.bottom.ModuleBottomFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:layout="@layout/fragment_module_bottom"/>
</RelativeLayout>
</android.support.design.widget.CoordinatorLayout></RelativeLayout>
2. Bottom Layer
The bottom layer view is relatively simple, just a TextView, so it is not further split.
package com.benhero.design.module.bg;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.benhero.design.R;
/**
* Modularized Background Fragment
*/
public class ModuleBgFragment extends Fragment {
public ModuleBgFragment() {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_module_bg, container, false);
}
}
3. Drawer
①. Fragment
package com.benhero.design.module.bottom;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.benhero.design.R;
/**
* Modularized Drawer Fragment
*/
public class ModuleBottomFragment extends Fragment {
private ModuleBottomPeekViewHolder mPeekViewHolder;
private ModuleBottomListViewHolder mListViewHolder;
public ModuleBottomFragment() {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View layout = inflater.inflate(R.layout.fragment_module_bottom, container, false);
mPeekViewHolder = new ModuleBottomPeekViewHolder(this.getActivity(), layout.findViewById(R.id.bottom_peek_layout));
mListViewHolder = new ModuleBottomListViewHolder(layout.findViewById(R.id.fragment_bottom_sheet_list));
return layout;
}
}
Here we have divided the drawer into two modules through ViewHolder. Additionally, the layout XML is as follows.
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#6CD1CC"
android:orientation="vertical"
tools:context="com.benhero.design.module.bottom.ModuleBottomFragment">
<LinearLayout
android:id="@+id/bottom_peek_layout"
android:layout_width="match_parent"
android:layout_height="@dimen/main_bottom_sheet_peek_height"
android:orientation="horizontal">
<Button
android:id="@+id/bottom_peek_btn_1"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="Btn 1"/>
<Button
android:id="@+id/bottom_peek_btn_2"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="Btn 2"/>
</LinearLayout>
<include
layout="@layout/fragment_module_bottom_list"/>
</LinearLayout>
②. ViewHolder
package com.benhero.design.module.bottom;
import android.content.Context;
import android.view.View;
import android.widget.Toast;
import com.benhero.design.R;
import com.benhero.design.module.base.ViewHolder;
/**
* Drawer Top ViewHolder
*
* @author benhero
*/
public class ModuleBottomPeekViewHolder extends ViewHolder implements View.OnClickListener {
private final Context mContext;
private View mBtn1;
private View mBtn2;
public ModuleBottomPeekViewHolder(Context context, View contentView) { super(contentView);
mContext = context;
initView();
}
private void initView() {
View contentView = getContentView();
mBtn1 = contentView.findViewById(R.id.bottom_peek_btn_1);
mBtn2 = contentView.findViewById(R.id.bottom_peek_btn_2);
mBtn1.setOnClickListener(this);
mBtn2.setOnClickListener(this);
}
@Override
public void onClick(View view) {
if (view.equals(mBtn1)) {
Toast.makeText(mContext, "Click Btn1", Toast.LENGTH_SHORT).show();
} else if (view.equals(mBtn2)) {
Toast.makeText(mContext, "Click Btn2", Toast.LENGTH_SHORT).show();
}
}
}
Github
The case in this article has been uploaded to Github – DesignExplore
Conclusion
The usage scenarios for the above two patterns are roughly as follows.
-
When the interface view cannot be cut into modularization, and there is no possibility of reuse for views and logic: use MVP, and no need to abstract interfaces
-
In cases where the interface view or logic has the possibility of reuse: use MVP and abstract interfaces
-
When the interface view can be modularized, with less association between modules: use the view modularization approach: Activity→Fragment→ViewHolder
-
If the interface is very complex, consider using both methods simultaneously
For the modularization solution, communication between different modules can use interfaces to let the upper layer transfer. A simpler option is to use EventBus.
For program design, everyone’s understanding may be different, but our goal is the same: to achieve better readability, logic, and scalability of the program.
Blog link address:
Ben’s blog address:
http://www.jianshu.com/p/fb057953131e
The Terminal R&D Department advocates: There is nothing that cannot be done, only what cannot be thought of.
What you gain here is not just technology!
Let the heart learn to dance in the sunlight
Let the soul learn to smile in pain
—Terminal R&D Department—
If you find this article helpful, feel free to join the QQ group: QQ group 232203809
WeChat public account: Terminal R&D Department
What you learn here is not just technology
Leave a Comment
Your email address will not be published. Required fields are marked *