This article is mainly based on their own understanding of MVP to build the MVP framework in line with their own business scenarios.
Put a Demo address first, also at the end of the article
About the MVP
- M (Model) is responsible for data request, analysis, filtering and other data operations.
- V (View) handles the UI, usually with
Activity
Fragment
Appears in the form of. - P (Presenter) View Model middleware, interactive bridge.
The figure above is quoted from
The benefits of the MVP
- Decoupled UI logic from business logic, reducing coupling.
- The Activity only handles UI-related operations, making the code more concise.
- UI logic and business logic are abstracted into interfaces for easy reading and maintenance.
- Pull business logic into Presenter to avoid memory leaks caused by complex business logic.
The specific implementation
1. View encapsulation In general, do data requests have display loading box, request success, request failure and other operations, we encapsulated these common functions into BaseView.
Public interface IBaseView {/** * display loading box */ void showLoading(); /** * dismissLoading(); /** * empty data ** @param tag */ void onEmpty(Object tag); /** * error data ** @param tag tag * @param errorMsg Error message */ void onError(Object tag, String errorMsg); /** * context ** @return context
*/
Context getContext();
}
Copy the code
To avoid memory leaks caused by time-consuming operations performed by presenters holding views, our presenters should be created and destroyed at the same time as the host Activity/Fragment.
public abstract class BasePresenter{ ... Public void attachView(View) {this. View = View; } /** * unbind View */ public voiddetachView() { this.view=null; }... } public abstract class MvpActivity<P extends BasePresenter> extends BaseActivity implements IBaseView{ ... @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); Present Presenter = createPresenter();if(presenter ! = null) { presenter.attachView(this); } } @Override protected voidonDestroy() {
super.onDestroy();
if(presenter ! = null) { presenter.detachView(); presenter = null; }}... }Copy the code
The above actions can solve the memory leak problem, but they can cause problems in the line:
Scenario: When a user opens the commodity list page, the network is poor and data acquisition is slow. The user leaves the page and continues to browse other pages, and the application crashes suddenly.
Analysis problem: P and V are bound when the user opens the page, and P and V are unbound when the user leaves the page. When the time-consuming operation is completed and the update interface of V is called, since P and V have been unbound and V is null, calling the update page method of V will cause null pointer exception.
Fix the problem: Use a dynamic proxy to weak reference a View. The complete BasePresenter is as follows:
public abstract class BasePresenter<M extends IBaseModel, V extends IBaseView> { private V mProxyView; private M module; private WeakReference<V> weakReference; /** * bind View */ @suppressWarnings ("unchecked")
public void attachView(V view) {
weakReference = new WeakReference<>(view);
mProxyView = (V) Proxy.newProxyInstance(
view.getClass().getClassLoader(),
view.getClass().getInterfaces(),
new MvpViewHandler(weakReference.get()));
if(this.module == null) { this.module = createModule(); } /** * unbind View */ public voiddetachView() {
this.module = null;
if(isViewAttached()) { weakReference.clear(); weakReference = null; }} /** * Whether to connect to View */ protected BooleanisViewAttached() {
returnweakReference ! = null && weakReference.get() ! = null; } protected VgetView() {
return mProxyView;
}
protected M getModule() {
return module;
}
protected Context getContext() {
return getView().getContext();
}
protected void showLoading() {
getView().showLoading();
}
protected void dismissLoading() { getView().dismissLoading(); } /** * createModule */ protected createModule(); Public void start(); public void start(); /** * private class MvpViewHandler implements InvocationHandler {private IBaseView implements InvocationHandler mvpView; MvpViewHandler(IBaseView mvpView) { this.mvpView = mvpView; } @override public Object invoke(Object proxy, Method Method, Object[] args) throws Throwable {// If layer V is not destroyed, execute the layer V Method.if (isViewAttached()) {
returnmethod.invoke(mvpView, args); } // The P layer does not care about the return value of the V layerreturnnull; }}}Copy the code
Contract classes manage all interfaces of Model, View and Presenter through Contract classes. In this way, the functions of Presenter and View are clear and easy to maintain. At the same time, View and Presenter correspond one by one. And effectively reduce the number of classes.
public interface Contract { interface Model extends IBaseModel { void login(User user, ResponseCallback callback); } interface View extends IBaseView { User getUserInfo(); void loginSuccess(User user); } interface Presenter { void login(); }}Copy the code
4. The same is true for Fragment encapsulation of activities
public abstract class BaseMvpActivity<P extends BasePresenter> extends Activity implements IBaseView {
protected P presenter;
@SuppressWarnings("unchecked") @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); Present Presenter = createPresenter();if(presenter ! = null) { presenter.attachView(this); } } @Override protected voidonDestroy() {
super.onDestroy();
if(presenter ! = null) { presenter.detachView(); presenter = null; } } @Override public voidshowLoading() {
if(loadingDialog ! = null && ! loadingDialog.isShowing()) { loadingDialog.show(); } } @Override public voiddismissLoading() {
if(loadingDialog ! = null && loadingDialog.isShowing()) { loadingDialog.dismiss(); } } @Override public void onEmpty(Object tag) { } @Override public void onError(Object tag, String errorMsg) { } @Override public ContextgetContext() {
returnmContext; } /** * createPresenter */ protected createPresenter(); }Copy the code
By specifying Presenter by generics and exposing the abstract method createPresenter() to subclasses to createPresenter, the base class implements the common methods in BaseView, reducing the redundancy of subclass code. 5. Login case
Public interface LoginContract {interface Model extends IBaseModel {/** * login ** @param user user information * @param callback */ void login(User user, ResponseCallback callback); } interface View extends IBaseView {/** * returns User information */ User getUserInfo(); /** * loginSuccess */ void loginSuccess(User User); } interface Presenter {/** * login */ void login(); }}Copy the code
Model
public class LoginModel implements LoginContract.Model {
@Override
public void login(User user, ResponseCallback callback) {
if (user == null) {
callback.onError("", (Throwable) new Exception("User information is empty"));
}
RequestParam param = new RequestParam();
param.addParameter("username", user.getUsername());
param.addParameter("password", user.getPassword()); HttpUtils.getInstance() .postRequest(Api.LOGIN, param, callback); }}Copy the code
Presenter
public class LoginPresenter extends BasePresenter<LoginContract.Model, LoginContract.View>
implements LoginContract.Presenter {
@Override
public void login() {
if (isViewAttached()) {
getView().showLoading();
getModule().login(getView().getUserInfo(), new OnResultObjectCallBack<User>() {
@Override
public void onSuccess(boolean success, int code, String msg, Object tag, User response) {
if(code == 0 && response ! = null) { getView().loginSuccess(response); }else {
getView().onError(tag, msg);
}
}
@Override
public void onFailure(Object tag, Exception e) {
getView().onError(tag, msg);
}
@Override
public void onCompleted() { getView().dismissLoading(); }}); } } @Override protected LoginModelcreateModule() {
return new LoginModel();
}
@Override
public void start() {}}Copy the code
Log on to the Activity
public class LoginActivity extends ActionBarActivity<LoginPresenter> implements LoginContract.View {
@BindView(R2.id.edt_name)
EditText edtName;
@BindView(R2.id.edt_pwd)
EditText edtPwd;
@BindView(R2.id.ob_login)
ObserverButton obLogin;
@BindView(R2.id.ob_register)
TextView obRegister;
@Override
protected int getLayoutId() {
return R.layout.user_activity_login;
}
@Override
protected void initView() {
setTitleText("Login");
obLogin.observer(edtName, edtPwd);
}
@OnClick({R2.id.ob_login, R2.id.ob_register})
public void onViewClicked(View view) {
int i = view.getId();
if (i == R.id.ob_login) {
presenter.login();
} else if (i == R.id.ob_register) {
ActivityToActivity.toActivity(mContext, RegisterActivity.class);
}
}
@Override
public void loginSuccess(User user) {
UserInfoUtils.saveUser(user);
EventBusUtils.sendEvent(new Event(EventAction.EVENT_LOGIN_SUCCESS));
finish();
}
@Override
public void onError(Object tag, String errorMsg) {
super.onError(tag, errorMsg);
ToastUtils.showToast(mContext, errorMsg);
}
@Override
protected LoginPresenter createPresenter() {
return new LoginPresenter();
}
@Override
public void onEventBus(Event event) {
super.onEventBus(event);
if (TextUtils.equals(event.getAction(), EventAction.EVENT_REGISTER_SUCCESS)) {
finish();
}
}
@Override
protected boolean regEvent() {
return true;
}
@Override
public User getUserInfo() {
returnnew User(edtName.getText().toString().trim(), edtPwd.getText().toString().trim()); }}Copy the code
conclusion
Whether it’s MVP or MCV or MVVM, the goal is to separate the business from the UI and avoid cramming all operations into one Activity, each working in its own domain. Everyone has a different understanding and perspective on hierarchy, and it is important to encapsulate a framework that is right for you and the current business scenario.
The MVP structure is used in this framework
Finally put the Demo address, study together, what is not good, welcome to point out!
A brief introduction to the MVP architecture in Android in-depth explanation of the Android MVP framework