Exploring Android Programming: MVP and Modularization

Exploring Android Programming: MVP and Modularization

Introduction

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

MVP Introduction

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.

Exploring Android Programming: MVP and Modularization

Exploring Android Programming: MVP and Modularization

①. 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

Exploring Android Programming: MVP and Modularization

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.

Exploring Android Programming: MVP and Modularization

Exploring Android Programming: MVP and Modularization

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

Exploring Android Programming: MVP and Modularization
Exploring Android Programming: MVP and Modularization

What you learn here is not just technology

Leave a Comment

Your email address will not be published. Required fields are marked *