Android: Simple understanding and use of the MVP mode of Android study notes

MVP mode

1. Why use the MVP pattern?

Why introduce architecture? The project that introduces the architecture must reach a certain scale, that is, there is a certain degree of coupling and redundancy, and it also violates the object-oriented single responsibility principle in a certain sense.

Then the problem that MVP solves is obvious, that is 冗余、混乱、耦合重. Putting aside the MVP at this time, if we want to find a way to solve it ourselves, how to solve it?

分而治之, We may think that, according to 单一职责原则the redundancy of Activity or Fragment or other components, it is necessary to divide different responsibility modules according to different functional modules, so as to follow the principle of single responsibility. Standing on the wisdom of the predecessors, perhaps many people have thought of it M(Model)V(View)C(Controller). We can learn from this development model to achieve our goal, temporarily dividing a page into

UI模块,也即View层

Model模块,也即数据请求模块

Logic模块, 司逻辑处理

In this way, the division of responsibilities is clear first, and the problems of confusion and redundancy are solved.

  • A project follows a single responsibility from subcontracting, to classification, and finally split method implementation;
  • The more atomic a division of responsibilities is, the better its reusability is, of course, it also depends on the actual business. For example the following code:

1.1. Examples

insert image description here

public class LoginActivity extends AppCompatActivity {
    
    

    EditText inputUserName;
    EditText inputPassword;
    Button btnLogin;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);

        btnLogin.setOnClickListener(new View.OnClickListener() {
    
    
            @Override
            public void onClick(View v) {
    
    

                final String userName = inputUserName.getText().toString();
                final String password = inputPassword.getText().toString();

                boolean isEmptyUserName = userName == null || userName.length() == 0;
                boolean isEmptyPassword = userName == null || userName.length() == 0;

                boolean isUserNameValid =Pattern.compile("^[A-Za-z0-9]{3,20}+$").matcher(userName   ).matches()
                boolean isPasswordValid = Pattern.compile("^[A-Za-z0-9]{3,20}+$").matcher(password ).matches()

                if (isEmptyPassword || isEmptyPassword) {
    
    
                    Toast.makeText(LoginActivity.this, "请输入帐号密码", Toast.LENGTH_SHORT).show();
                } else {
    
    
                    if (isUserNameValid && isPasswordValid) {
    
    
                        new Thread(new Runnable() {
    
    
                            @Override
                            public void run() {
    
    
                                // ...登录请求
                                boolean loginResult = false;

                                if (loginResult) {
    
    
                                    Toast.makeText(LoginActivity.this, "登录成功", Toast.LENGTH_SHORT).show();
                                } else {
    
    
                                    Toast.makeText(LoginActivity.this, "登录失败", Toast.LENGTH_SHORT).show();
                                }
                            }
                        }).start();
                    } else {
    
    
                        Toast.makeText(LoginActivity.this, "帐号密码格式错误", Toast.LENGTH_SHORT).show();
                    }
                }
            }
        });
    }
}

A simple login, including 点击事件, 获取登录信息, 判断是否空, 校验是否正确, 请求登录, 返回处理.

  • Such code structure is confusing, poor readability; redundant code, poor reusability;
  • The codes of different functions are mixed together, and the coupling is high. This is just a very simple little function.

As mentioned above, according to the object-oriented single responsibility principle, the more atomic the division of a module is, that is, the finer the division, the higher the reusability. If I change to this

public class LoginActivity extends AppCompatActivity {
    
    

    EditText inputUserName;
    EditText inputPassword;
    Button btnLogin;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);

        btnLogin.setOnClickListener(new View.OnClickListener() {
    
    
            @Override
            public void onClick(View v) {
    
    
                final String userName = getEditorText(inputUserName);
                final String password = getEditorText(inputPassword);

                if (isEmpty(userName) || isEmpty(password)) {
    
    
                    showTips("请输入帐号密码");
                } else {
    
    
                    if (isValid(userName) && isValid(password)) {
    
    
                        // 登录
                        doLogin(userName, password);
                    } else {
    
    
                        showTips("帐号密码格式错误");
                    }
                }
            }
        });
    }

    private boolean isValid(String s) {
    
    
        return Pattern.compile("^[A-Za-z0-9]{3,20}+$").matcher(s).matches();
    }

    private boolean isEmpty(String s) {
    
    
        return s == null || s.length() == 0;
    }

    private String getEditorText(EditText et) {
    
    
        return et.getText().toString();
    }

    private void showTips(String tips) {
    
    
        Toast.makeText(LoginActivity.this, tips, Toast.LENGTH_SHORT).show();
    }

    private void doLogin(String username, String password) {
    
    
        new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                // ...登录请求
                boolean loginResult = false;
                // 更新UI
                notifyLoginResult(loginResult);
            }
        }).start();
    }

    private void notifyLoginResult(boolean loginResult) {
    
    
        if (loginResult) {
    
    
            showTips("登录成功");
        } else {
    
    
            showTips("登录失败");
        }
    }

}

After splitting the source code method, isEmpty, isValid, showTips... etc., the results produced are bright:

  • 1) After the method is split, the reusability is improved
  • 2) In comparison, after browsing, I can basically understand what is done in onClick, that is, the structure is clear

This is the role of the single responsibility principle, which improves reusability, reduces code redundancy, and begins to reveal a clear line of thinking.

The above illustrates the significance of a single responsibility and the additional benefits it brings. Then after the initial refactoring of the code, although it is clearer and the redundancy is eliminated, the problem of coupling remains. So how to solve the coupling problem? let's watch the second half

2. Let you understand MVP step by step

One of the most difficult aspects of MVP: How to correctly divide each module

  • Model is very simple, the boundary of data loading is very clear, it is easy to divide, such as database operations, such as file queries, such as network requests, can be taken out together with asynchronous operations, and divided into separate Model layers.

The interaction between the View layer and the Presenter layer is very frequent. Many people don't know whether this piece of code is a View or a Presenter.

  • First of all, pure logic implementation must be handled by Presenter; simple View initialization must also be handled by View, such as findView.

Like the login module, View and logic are intertwined, how to distinguish them?

First of all, the Login function is roughly divided into the following sub-functions:

1. Value, EditText account number and password (clear View layer, does not involve logical operations)
2. Null judgment and verification (Presenter but involves View, because the account number and password are used, through the form of parameter passing)
3. Login request ( A veritable Model, the processing is obviously in the Presenter layer)
4. Update the UI (View layer)

In fact, the boundaries of the above divisions are relatively clear. It is inevitable to encounter some bad boundaries in the project. Let me teach you a trick. Difficult divisions must include View and logic processing. So the first step,

  • Atomic splitting separates View and logic processing into different methods. The View part is in the View layer, and the processing part is in the Presenter layer
  • There are some divisions of Toast, Dialog, etc., which are distinguished according to Context.
    • Can be implemented using Application Context, can be used as Presenter layer; must use Activity
      Context, as View layer

Then the split of MVP is clarified, let’s take a look at the split result

2.1. The first step of MVP implementation is to split the page into three modules: M/V/P

1. View part

public class LoginActivity extends AppCompatActivity {
    
    

    EditText inputUserName;
    EditText inputPassword;
    Button btnLogin;

    LoginPresenter presenter;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);

        presenter = new LoginPresenter(this);

        btnLogin.setOnClickListener(new View.OnClickListener() {
    
    
            @Override
            public void onClick(View v) {
    
    
                presenter.execureLogin(getEditorText(inputUserName), getEditorText(inputPassword));
            }
        });
    }

    private String getEditorText(EditText et) {
    
    
        return et.getText().toString();
    }

    public void showTips(String tips) {
    
    
        Toast.makeText(LoginActivity.this, tips, Toast.LENGTH_SHORT).show();
    }

    public void notifyLoginResult(boolean loginResult) {
    
    
        if (loginResult) {
    
    
            showTips("登录成功");
        } else {
    
    
            showTips("登录失败");
        }
    }

}

2. Model part

public class LoginModel {
    
    
    private Handler handler;

    public LoginModel() {
    
    
        handler = new Handler();
    }

    public interface OnLoginCallback {
    
    
        void onResponse(boolean success);
    }

    public void login(String username, String password, final OnLoginCallback callback) {
    
    
        new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                // ...请求接口
                boolean result = true; // 假设这是接口返回的结果
                callback.onResponse(result);
            }
        }).start();
    }
}

Presenter section

public class LoginPresenter {
    
    

    private LoginModel model;
    private LoginActivity activity;
    private String verifyMsg;

    public LoginPresenter(LoginActivity activity) {
    
    
        this.activity = activity;
        model = new LoginModel();
    }

    public void execureLogin(String username, String password) {
    
    
        boolean verifyBefore = verifyBeforeLogin(username, password);
        if (verifyBefore) {
    
    
            // 校验通过,请求登录
            model.login(username, password, new LoginModel.OnLoginCallback() {
    
    
                @Override
                public void onResponse(boolean success) {
    
    
                    // 登录结果
                    activity.notifyLoginResult(success);
                }
            });
        } else {
    
    
            // 校验失败,提示
            activity.showTips(verifyMsg);
        }
    }

    private boolean verifyBeforeLogin(String username, String password) {
    
    
        boolean isEmpty = isEmpty(username) || isEmpty(password);
        boolean isValid = isValid(username) && isValid(password);
        if (isEmpty) {
    
    
            verifyMsg = "请输入帐号或密码";
            return false;
        }
        if (isValid) {
    
    
            return true;
        }
        verifyMsg = "帐号或密码错误";
        return false;
    }

    private boolean isValid(String s) {
    
    
        return Pattern.compile("^[A-Za-z0-9]{3,20}+$").matcher(s).matches();
    }

    private boolean isEmpty(String s) {
    
    
        return s == null || s.length() == 0;
    }
}

It can be seen from the above code:

  • 1. Toast prompts, update login status, etc., are all split in the View layer;
  • 2. Verification and login are split in the Presenter layer;
  • 3. The network request is split into the Model layer.

In this way, each layer only handles the business of this layer, and splits the single responsibility in a general direction, so that the whole conforms to the principle of single responsibility.

According to MVP, the page is split into 3 layers, and we have fully complied with the principle of single responsibility. But if you look carefully, you suddenly find that there are still dependencies between them, and the decoupling effect is not so ideal. Then we have to think, what causes the coupling to still exist?

That's object holding, see our project

  • Presenter holds the View (Activity) object and holds the Model object at the same time
  • View holds Presenter object

How does MVP solve the object holding problem?

  • programming to interface

2.2, MVP implements the second step, using interface communication, further decoupling

For object-oriented design, the use of interfaces to achieve decoupling is well known. This change is very small, just change the object holding to the interface holding.

  • View holds the Presenter object instead of holding the Presenter interface
  • The Presenter holds the View object instead of holding the View interface

Since you have the interface, you must implement the interface for external calls in View and Presenter respectively.

  • The methods that View calls for Presenter are notifyLoginResultand showTips;
  • There are methods that Presenter can call for View executeLogin.

So how about implementing the interface first? look at the code

Presenter interface

public interface IPresenter {
    
    
    /**
     * 执行登录
     *
     * @param username
     * @param password
     */
    void executeLogin(String username, String password);
}

View interface

public interface IView {
    
    
    /**
     * 更新登录结果
     *
     * @param loginResult
     */
    void notifyLoginResult(boolean loginResult);

    /**
     * Toast提示
     *
     * @param tips
     */
    void showTips(String tips);
}

The role of the interface is to provide a specification for external calls. So here we abstract the methods that need to be called externally and add them to the interface. The interface is available, and the interface represents the implementation of View or Presenter, so implement them separately. look at the code

Presenter implements the interface

public class LoginPresenter implements IPresenter {
    
    

    private LoginModel model;
    private LoginActivity activity;
    private String verifyMsg;

    public LoginPresenter(LoginActivity activity) {
    
    
        this.activity = activity;
        model = new LoginModel();
    }

    @Override
    public void executeLogin(String username, String password) {
    
    
        boolean verifyBefore = verifyBeforeLogin(username, password);
        if (verifyBefore) {
    
    
            // 校验通过,请求登录
            model.login(username, password, new LoginModel.OnLoginCallback() {
    
    
                @Override
                public void onResponse(boolean success) {
    
    
                    // 登录结果
                    activity.notifyLoginResult(success);
                }
            });
        } else {
    
    
            // 校验失败,提示
            activity.showTips(verifyMsg);
        }
    }

    private boolean verifyBeforeLogin(String username, String password) {
    
    
        boolean isEmpty = isEmpty(username) || isEmpty(password);
        boolean isValid = isValid(username) && isValid(password);
        if (isEmpty) {
    
    
            verifyMsg = "请输入帐号或密码";
            return false;
        }
        if (isValid) {
    
    
            return true;
        }
        verifyMsg = "帐号或密码错误";
        return false;
    }

    private boolean isValid(String s) {
    
    
        return Pattern.compile("^[A-Za-z0-9]{3,20}+$").matcher(s).matches();
    }

    private boolean isEmpty(String s) {
    
    
        return s == null || s.length() == 0;
    }
}

View implements the interface

public class LoginActivity extends AppCompatActivity implements IView{
    
    

    EditText inputUserName;
    EditText inputPassword;
    Button btnLogin;

    LoginPresenter presenter;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);

        presenter = new LoginPresenter(this);

        btnLogin.setOnClickListener(new View.OnClickListener() {
    
    
            @Override
            public void onClick(View v) {
    
    
                presenter.executeLogin(getEditorText(inputUserName), getEditorText(inputPassword));
            }
        });
    }

    private String getEditorText(EditText et) {
    
    
        return et.getText().toString();
    }

    @Override
    public void showTips(String tips) {
    
    
        Toast.makeText(LoginActivity.this, tips, Toast.LENGTH_SHORT).show();
    }

    @Override
    public void notifyLoginResult(boolean loginResult) {
    
    
        if (loginResult) {
    
    
            showTips("登录成功");
        } else {
    
    
            showTips("登录失败");
        }
    }

}

Provide methods for external calls in the interface, and then implement them in View and Presenter respectively. Both the interface and the implementation are available, remember what our purpose is? Yes 把持有的对象替换为接口, roll up and look at the code

// 这是View持有的接口,在onCreate中初始化的对象由原来的LoginPresenter改为了IPresenter。
public class LoginActivity extends AppCompatActivity implements IView{
    
    

    EditText inputUserName;
    EditText inputPassword;
    Button btnLogin;

    IPresenter presenter;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);

        presenter = new LoginPresenter(this);
        ...
}

// 这是Presenter持有的接口,在构造中由原来的LoginActivity改为了IView

public class LoginPresenter implements IPresenter {
    
    

    private LoginModel model;
    private String verifyMsg;

    private IView activity;

    public LoginPresenter(IView activity) {
    
    
        this.activity = activity;
        model = new LoginModel();
    }

    ...

2.2.1. The object-oriented principles followed by MVP

1) Single Responsibility

  • Each module is only responsible for its own work, and does not cross the boundary. For example, View is responsible for UI initialization and updating, and Model is responsible for data query and asynchrony. As for logical judgment and business implementation, just throw it in Presenter without worry.

2) Interface-oriented communication

  • Object holding is one of the essential causes of coupling, so to achieve the purpose of decoupling, it is most appropriate to replace it with interface holding.

2.3. Encapsulate a complete MVP framework from beginning to end

2.3.1. Business Analysis

Take login as an example here. Let's analyze the business first:

1. EditText obtains the input UserName and Password

2. Verification (including judging whether it is empty, whether it conforms to the input specification, such as not allowing special characters to be input)

3. If the verification is passed, execute the login request; if it fails, an error will be prompted directly

4. Login request

5. According to the login result, it prompts that the login is successful or failed

6. Update the UI with the login result

2.3.2, the concept of BaseInterface and Contract

MVP introduces the concepts of BaseInterface and Contract. If it is pure mvp, many people may understand it, but adding these two concepts makes it more difficult to understand.

base-interface is our commonly used base concept, the purpose is to standardize and unify the operation. For example, display a Toast, determine whether the network is connected, jump animation, etc., we put them in BaseActivity, because all activities need these. Interface inheritance is also for this purpose. Such as login function

  • 1) We need a Presenter, so we have LoginPresenter
  • 2) We need a LoginPresenter interface to provide calls for the View layer, so we have ILoginPresenter
  • 3) Regardless of login, registration, or other functions, all Presenters need a function start, so there is IPresenter
    • IPresenter provides an operation common to all Presenter interfaces, which is start, which is the concept of initial loading Contract

The introduction of this concept is only for unified management of the View and Presenter interfaces of a page. Each page corresponds to

  • 1个View(Activity或Fragment), 一个IView(View接口)
  • 1个Presenter,一个IPresenter(Presenter接口)
  • 1个Contract(一个包含View接口和Presenter接口的接口)

like

public interface LoginContract {
    
    

    interface View {
    
    
        void notifyLoginResult();
    }

    interface Presenter {
    
    
        void login(String username, String password);
    }

}

2.3.3. Encapsulate a complete MVP framework from beginning to end.

1) Let's think first, what should we define first? Of course the public interface.

The public interface of View (IView in MVP-Samples) has no public operations. We define an empty interface for unified specification.

public interface IView {
    
    
}

Presenter's public interface (IPresenter in MVP-Samples) also has no public operations. The samples provided by mvp have a start, but it is not needed here.

  • why? Because we also have a BasePresenter. So we still define an empty interface for unified specification.
public interface IPresenter {
    
    
}

The above two interfaces, yes 用于给View与Presenter的接口继承的, note that they are not inherited by View or Presenter itself. Because it defines the specification of the interface, and the interface is the specification of the defined class.

2) The defined interface should inherit IView and IPresenter, and be managed by Contract

public interface LoginContract {
    
    

    interface View extends IView {
    
    

        // View中的2个功能:
        // 1) 取得登录需要的username, password # 不需要对Presenter层提供调用
        // 2) 提示错误信息, 提示登录结果 # 需要Presenter层调用,因为校验和登录都是在Presenter层的
        // 因此2)是View层提供的对外方法,需要在接口中定义

        /**
         * 提示一个Toast
         *
         * @param msg
         */
        void showToast(String msg);

    }

    interface Presenter extends IPresenter {
    
    

        // Presenter中的2个功能:
        // 1) 校验 # 看你怎么写,既可以在View层中调用校验方法,也可以在Presenter层中,这里定义为直接在Presenter中校验,彻底和View解耦
        // 2) 登录 # 先执行校验,再执行登录,需要在View层点击登录时调用
        // 因此2)是Presenter对外层提供的方法,需要在接口中定义

        /**
         * 登录操作
         *
         * @param username
         * @param password
         */
        void login(String username, String password);

    }
}

The above Contract (referred to as the function warehouse) defines the View and Presenter interfaces respectively, and adds the definition process analysis of the interfaces.

3) The interface definition is complete, what is the next step? It must be to implement the interface and add functions. Define Presenter and View to implement interfaces respectively, and add corresponding functions.

public class LoginActivity extends AppCompatActivity implements LoginContract.View {
    
    

    LoginContract.Presenter mPresenter;

    Button loginBtn;
    EditText etUser, etPwd;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);

        mPresenter = new LoginPresenter(this);

        loginBtn.setOnClickListener(new View.OnClickListener() {
    
    
            @Override
            public void onClick(View v) {
    
    
                mPresenter.login(etUser.getText().toString(), etPwd.getText().toString());
            }
        });
    }

    @Override
    public void showToast(String msg) {
    
    
        Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
    }

*********************************************************************************************************
public class LoginPresenter implements LoginContract.Presenter {
    
    

    LoginContract.View mView;

    public LoginPresenter(LoginContract.View mView) {
    
    
        this.mView = mView;
    }

    @Override
    public void login(String username, String password) {
    
    
        // 校验直接放在登录流程
        boolean isVerifySuccessully = verifyLoginInfo();
        if (isVerifySuccessully) {
    
    
            // 请求登录
            LoginModel.requestLogin(username, password, new LoginModel.RequestCallback() {
    
    
                @Override
                public void onResponse(boolean result) {
    
    
                    if (result) {
    
    
                        // 提示登录成功
                        mView.showToast("登录成功");
                    } else {
    
    
                        // 提示登录失败
                        mView.showToast("登录失败");
                    }
                }
            });
        } else {
    
    
            // 校验失败,提示错误
            mView.showToast("无效的帐号或密码");
        }
    }

    private boolean verifyLoginInfo() {
    
    
        // 这里校验登录信息
        // 校验帐号,密码是否为空
        // 校验帐号,密码是否符合要求
        return true;
    }
}

Note: LoginActivity needs to destroy the Presenter reference in the relative life cycle. Since it will be encapsulated later, it is not added here.

  • There may be a memory leak. If the model layer data is not completed by the callback, the activity exits, thinking that the presenter holds the current activity, so the gc cannot, and a memory leak occurs

At this point, it is basically a complete MVP development model. From the division of levels to interface communication, it is actually quite simple, isn't it? Let's continue to optimize this framework, we consider the following issues:

  • Every Activity or Fragment needs to initialize or manage Presenter, tired?
  • Similarly, every Presenter has to manage the View, tired?

2.4, the final optimized version

Presenter base class extraction, what are the common elements:

  • Presenter public elements, in fact, there are two main: Context, Viewinterface.
    • Note: Presenter should not pass in the Activity's Context;
      • If you need to use the Context of Activity, then the Presenter layer is not simple. Then it can only be the Context of Application.
      • There are two ways for us to obtain Application Context, static acquisition of AppContext (your Application) and getApplicationContext of Activity. Use the incoming Application Context here!

The acquisition of many online Views is the method of defining an AttachView, which is used here and passed directly in the construction.


public abstract class BasePresenter<AttachView extends IView> {
    
    
    private Context mContext;
    private AttachView mView;

    public BasePresenter(Context context, AttachView view) {
    
    
        if (context == null) {
    
    
            throw new NullPointerException("context == null");
        }
        mContext = context.getApplicationContext();
        mView = view;
    }

    /**
     * 获取关联的View
     *
     * @return
     */
    public AttachView getAttachedView() {
    
    
        if (mView == null) {
    
    
            throw new NullPointerException("AttachView is null");
        }
        return mView;
    }

    /**
     * 获取关联的Context
     *
     * @return
     */
    public Context getContext() {
    
    
        return mContext;
    }

    /**
     * 清空Presenter
     */
    public void clearPresenter() {
    
    
        mContext = null;
        mView = null;
    }

    /**
     * View是否关联
     *
     * @return
     */
    public boolean isViewAttached() {
    
    
        return mView != null;
    }

    /**
     * 网络是否连接
     *
     * @return
     */
    public boolean isNetworkConnected() {
    
    
        if (mContext == null) {
    
    
            throw new NullPointerException("mContext is null");
        }
        return NetworkHelper.isNetworkConnected(mContext);
    }

    public abstract void start();

    public abstract void destroy();
}

The above is the Presenter base class we extracted. Achieved:

1. Bind the View interface during initialization, and clear the interface when clearing
2. Automatically obtain the ApplicationContext (it is recommended not to do this, and directly pass the Application Context) 3. View status judgment
4. Network connection judgment (because the network request comparison is performed in the Presenter Frequently, you can customize multiple methods according to the business)
5. satrt initialization method and destroy destruction method (combined with the subsequent MVPCompatActivity automatic destruction)

Note: When using View, please first 判断View状态, otherwise it will report when the View is destroyed abnormally NullPoiterException. 如果有线程或者Handler一定要在destroy中销毁,避免造成内存泄漏.

for Activity

/**
 * MVP - Activity基类
 */
public abstract class MVPCompatActivity<T extends BasePresenter> extends RootActivity {
    
    
    protected T mPresenter;

    @Override
    protected void onStart() {
    
    
        super.onStart();
        if (mPresenter == null) {
    
    
            mPresenter = createPresenter();
        }
        mPresenter.start();
    }

    @Override
    protected void onStop() {
    
    
        super.onStop();
        mPresenter.clearPresenter();
        mPresenter = null;
    }

    @Override
    public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
    
    
        super.onSaveInstanceState(outState, outPersistentState);
        mPresenter.clearPresenter();
        mPresenter = null;
    }

    /**
     * 创建一个Presenter
     *
     * @return
     */
    protected abstract T createPresenter();

}

for Fragment


public abstract class MVPCompatFragment<T extends BasePresenter> extends RootFragment {
    
    
    protected T mPresenter;

    @Override
    public void onStart() {
    
    
        super.onStart();
        if (mPresenter == null) {
    
    
            mPresenter = createPresenter();
        }
        mPresenter.start();
    }

    @Override
    public void onStop() {
    
    
        super.onStop();
        if (mPresenter != null) {
    
    
            mPresenter.clearPresenter();
            mPresenter = null;
        }
    }

    protected abstract T createPresenter();

}

for Layout


public abstract class MVPCompatLayout<T extends BasePresenter> extends RootLayout {
    
    

    protected T mPresenter;

    public MVPCompatLayout(Context context, @Nullable AttributeSet attrs) {
    
    
        super(context, attrs);
    }

    @Override
    protected void onAttachedToWindow() {
    
    
        super.onAttachedToWindow();
        mPresenter = createPresenter();
    }

    @Override
    protected void onDetachedFromWindow() {
    
    
        super.onDetachedFromWindow();
        if (mPresenter != null) {
    
    
            mPresenter.clearPresenter();
            mPresenter = null;
        }
    }

    protected abstract T createPresenter();
}

for Adapter


public abstract class MVPCompatRecyclerAdapter<T, P extends BasePresenter> extends RootRecyclerAdapter<T> {
    
    
    protected P mPresenter;

    public MVPCompatRecyclerAdapter(Context context, List data) {
    
    
        super(context, data);
    }

    protected abstract P createPresenter();


    @Override
    public void onViewAttachedToWindow(RecyclerViewHolder holder) {
    
    
        super.onViewAttachedToWindow(holder);
        mPresenter = createPresenter();
    }

    @Override
    public void onViewDetachedFromWindow(RecyclerViewHolder holder) {
    
    
        super.onViewDetachedFromWindow(holder);
        if (mPresenter != null) {
    
    
            mPresenter.clearPresenter();
            mPresenter = null;
        }
    }
}

By inheriting the Base of the above View, initialization and destruction can be freely realized. Easy to achieve MVP.

Finally, the supplementary RootActivity, as a Base Activity, determines the content in it according to different businesses, so there are few


public abstract class RootActivity extends AppCompatActivity {
    
    
    protected Context mContext;
    protected Context mAppContext;

    private View mContentView;

    private Bundle mBundleObj;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        mAppContext = getApplicationContext();
        mContext = this;
        mContentView = getLayoutInflater().inflate(getLayoutRes(), null);
        setContentView(mContentView);
        ButterKnife.bind(this);
        init();
    }

    protected abstract int getLayoutRes();

    protected abstract void init();

    /**
     * findViewById
     *
     * @param resId
     * @param <T>
     * @return
     */
    protected <T extends View> T $(int resId) {
    
    
        return (T) findViewById(resId);
    }

    /**
     * Toast
     *
     * @param toast
     */
    protected void showToast(String toast) {
    
    
        Toast.makeText(this, toast, Toast.LENGTH_SHORT).show();
    }

    /**
     * get a bundle from reuse.
     *
     * @return
     */
    protected Bundle obtainBundle() {
    
    
        if (mBundleObj == null) {
    
    
            mBundleObj = new Bundle();
        } else {
    
    
            mBundleObj.clear();
        }
        return mBundleObj;
    }


}

3. Specific examples

insert image description here

3.1、MyUser

public class MyUser extends BmobUser {
    
    

    private String image;

    private String gender;

    private String age;

    public String getImage() {
    
    
        return image;
    }

    public void setImage(String image) {
    
    
        this.image = image;
    }

    public String getGender() {
    
    
        return gender;
    }

    public void setGender(String gender) {
    
    
        this.gender = gender;
    }

    public String getAge() {
    
    
        return age;
    }

    public void setAge(String age) {
    
    
        this.age = age;
    }
}

3.2、BaseContract

public interface BaseContract {
    
    

    interface BasePresenter<T> {
    
    

        void attachView(T view);

        void detachView();
    }

    interface BaseView {
    
    

        void onSuccess();

        void onFailure(Throwable e);


    }
}

3.3、LandContract

public interface LandContract extends BaseContract {
    
    

    interface View extends BaseContract.BaseView {
    
    

        void landSuccess(MyUser user);

    }

    interface Presenter extends BaseContract.BasePresenter<View>{
    
    
        /**
         * 用户登陆
         */
        void login(String username, String password);

        /**
         * 用户注册
         */
        void signup(String password, String mail);
    }
}

3.4、BaseMVPActivity

public abstract class BaseMVPActivity<T extends BaseContract.BasePresenter> extends BaseActivity{
    
    

    protected T mPresenter;

    protected abstract T bindPresenter();

    @Override
    protected void processLogic() {
    
    
        attachView(bindPresenter());
    }

    private void attachView(T presenter){
    
    
        mPresenter = presenter;
        mPresenter.attachView(this);
    }

    @Override
    protected void onDestroy() {
    
    
        super.onDestroy();
        mPresenter.detachView();
    }
}

3.5、LoginActivity

public class LoginActivity extends BaseMVPActivity<LandContract.Presenter>
        implements LandContract.View, View.OnFocusChangeListener, View.OnClickListener {
    
    

    private OwlView mOwlView;
    private EditText usernameET;
    private EditText passwordET;
    private EditText rpasswordET;
    private TextView signTV;
    private TextView forgetTV;
    private Button loginBtn;

    //是否是登陆操作
    private boolean isLogin = true;
    
    @Override
    protected int getLayoutId() {
    
    
        return R.layout.activity_user_land;
    }

    @Override
    protected void initWidget() {
    
    
        super.initWidget();
        mOwlView=findViewById(R.id.land_owl_view);
        usernameET=findViewById(R.id.login_et_username);

        passwordET=findViewById(R.id.login_et_password);
        rpasswordET=findViewById(R.id.login_et_rpassword);
        signTV=findViewById(R.id.login_tv_sign);
        forgetTV=findViewById(R.id.login_tv_forget);
        loginBtn=findViewById(R.id.login_btn_login);
    }

    @Override
    protected void initClick() {
    
    
        super.initClick();
        passwordET.setOnFocusChangeListener(this);
        rpasswordET.setOnFocusChangeListener(this);
        signTV.setOnClickListener(this);
        forgetTV.setOnClickListener(this);
        loginBtn.setOnClickListener(this);
    }

    @Override
    public void onFocusChange(View v, boolean hasFocus) {
    
    
        if (hasFocus) {
    
    
            mOwlView.open();
        } else {
    
    
            mOwlView.close();
        }
    }

    @Override
    public void onClick(View v) {
    
    
        switch (v.getId()) {
    
    
            case R.id.login_btn_login:  //button
                if (isLogin) {
    
    
                    login();  //登陆
                } else {
    
    
                    sign();  //注册
                }
                break;
            case R.id.login_tv_sign:  //sign
                if (isLogin) {
    
    
                    //置换注册界面
                    signTV.setText("登录");
                    loginBtn.setText("注册");
                    rpasswordET.setVisibility(View.VISIBLE);
                    usernameET.setVisibility(View.GONE);
                } else {
    
    
                    //置换登陆界面
                    signTV.setText("注册");
                    loginBtn.setText("登录");
                    usernameET.setVisibility(View.VISIBLE);
                    rpasswordET.setVisibility(View.GONE);
                }
                isLogin = !isLogin;
                break;
            case R.id.login_tv_forget:  //忘记密码
                showForgetPwDialog();
                break;
            default:
                break;
        }
    }

    /**
     * 执行登陆动作
     */
    public void login() {
    
    
        String username = usernameET.getText().toString();
        String password = passwordET.getText().toString();
        if (username.length() == 0 || password.length() == 0) {
    
    
            SnackbarUtils.show(mContext, "用户名或密码不能为空");
            return;
        }

        ProgressUtils.show(this, "正在登陆...");

        mPresenter.login(username, password);
    }

    /**
     * 执行注册动作
     */
    public void sign() {
    
    
        String email = usernameET.getText().toString();
        String password = passwordET.getText().toString();
        String rpassword = rpasswordET.getText().toString();
        if (email.length() == 0 || password.length() == 0 || rpassword.length() == 0) {
    
    
            SnackbarUtils.show(mContext, "请填写必要信息");
            return;
        }
        if (!StringUtils.checkEmail(email)) {
    
    
            SnackbarUtils.show(mContext, "请输入正确的邮箱格式");
            return;
        }
        if (!password.equals(rpassword)) {
    
    
            SnackbarUtils.show(mContext, "两次密码不一致");
            return;
        }

        ProgressUtils.show(this, "正在注册...");
        usernameET.setText(email);
        mPresenter.signup(password,email);

    }

    /***********************************************************************/

    @Override
    protected LandContract.Presenter bindPresenter() {
    
    
        return new LandPresenter();
    }

    @Override
    public void landSuccess(MyUser user) {
    
    
        ProgressUtils.dismiss();
        if (isLogin) {
    
    
            setResult(RESULT_OK, new Intent());
            Intent intent = new Intent(this, MainActivity1.class);
            startActivity(intent);
            finish();
        } else {
    
    
            Toast.makeText(this, "注册成功", Toast.LENGTH_SHORT).show();
            //置换登陆界面
            signTV.setText("注册");
            loginBtn.setText("登录");
            rpasswordET.setVisibility(View.GONE);
            usernameET.setVisibility(View.VISIBLE);
            isLogin=true;

        }
        Log.i(TAG,user.toString());
    }

    @Override
    public void onSuccess() {
    
    
        ProgressUtils.dismiss();
    }

    @Override
    public void onFailure(Throwable e) {
    
    
        ProgressUtils.dismiss();
        SnackbarUtils.show(mContext, e.getMessage());
        Log.e(TAG,e.getMessage());
    }

    /**
     * 显示忘记密码对话框
     */
    public void showForgetPwDialog() {
    
    
                new MaterialDialog.Builder(this)
                        .title("找回密码")
                        .inputType(InputType.TYPE_CLASS_TEXT)
                        .input("请输入注册邮箱", null, (dialog, input) -> {
    
    
                            String inputStr = input.toString();
                            if (input.equals("")) {
    
    
                                SnackbarUtils.show(mContext, "内容不能为空!");
                            } else if(!StringUtils.checkEmail(inputStr)) {
    
    
                                Toast.makeText(LoginActivity.this,
                                        "请输入正确的邮箱格式", Toast.LENGTH_LONG).show();
                            } else {
    
    
                                //找回密码
                                BmobUser.resetPasswordByEmail(input.toString(), new UpdateListener() {
    
    
                                    @Override
                                    public void done(BmobException e) {
    
    
                                        if (e == null) {
    
    
                                            ToastUtils.show(mContext, "重置密码请求成功,请到邮箱进行密码重置操作");
                                        } else {
    
    
                                            ToastUtils.show(mContext, "重置密码请求失败,请确认输入邮箱正确!");
                                        }
                                    }
                        });
                    }
                })
                .positiveText("确定")
                .negativeText("取消")
                .show();
    }

}

3.6、LandPresenter

public class LandPresenter extends RxPresenter<LandContract.View> implements LandContract.Presenter{
    
    
    private String TAG="LandPresenter";
    @Override
    public void login(String username, String password) {
    
    
        MyUser.loginByAccount(username, password, new LogInListener<MyUser>() {
    
    
            @Override
            public void done(MyUser myUser, BmobException e) {
    
    
                if(e == null) {
    
    
                    mView.landSuccess(myUser);
                }else {
    
    
                    String error=e.toString();
                    if(error.contains("incorrect")){
    
    
                        Toast.makeText(getApplicationContext(), "账号或者密码错误!", Toast.LENGTH_SHORT).show();
                        mView.onSuccess();
                    }else {
    
    
                        mView.onFailure(e);
                    }
                }
            }
        });
    }
    @Override
    public void signup(String password, String mail) {
    
    
        MyUser myUser =new MyUser();
        myUser.setPassword(password);
        myUser.setEmail(mail);
        myUser.signUp(new SaveListener<MyUser>() {
    
    
            @Override
            public void done(MyUser myUser, BmobException e) {
    
    
                if(e == null)
                    mView.landSuccess(myUser);
                else{
    
    
                    String error = e.toString();
                    if(error.contains("already")){
    
    
                        Toast.makeText(getApplicationContext(), "邮箱已经被注册,请重新填写!", Toast.LENGTH_SHORT).show();
                        mView.onSuccess();
                    }else {
    
    
                        mView.onFailure(e);
                    }
                }
            }
        });
    }
}

reference

1. Guide you to master MVP step by step
2. There are so many mobile architectures, how to handle them all at once
3. Android architecture MVC MVP MVVM+ example

Guess you like

Origin blog.csdn.net/JMW1407/article/details/123383885
Recommended