Best practices of MVP mode in Android Development

Source: Internet
Author: User

Best practices of MVP mode in Android Development

This article has been dragging on for a long time and has not been written in the draft box. If you are free today, please complete the preparation.

In retrospect, when you first learned Android, you will always see some books saying that Android uses the MVC mode and Activity is a Controller. Maybe at that time, you have no profound experience. With the accumulation of experience. You have found that Activity is a Controller and manages many business logic. It also acts as a part of the View and controls the display of the View layer. Over time, this Controller is too heavy and its responsibilities are no longer so simple.

Then, in order to make the Activity more uniform, the MVP and MVVM modes emerged, so we can only say that each has its own advantages. No one is right or wrong, one mode has characteristics that another mode does not have, and does not have the characteristics of another mode. The architecture is always selected based on the complexity of the business. MVC has its own characteristics, that is, it is simple to write code, but its disadvantages are also obvious. After the business is complicated, the Activity is too large and not very well maintained. As for MVVM, I personally reject this mode. Why? Writing data binding code in XML seems a little painful, so that the responsibilities of xml are not so single? In my opinion, xml is used as a View, so it is "not clean" without adding any other elements ". What about MVP? I think in Android development, MVP is a model worth considering. It does not write data binding code in xml like MVVM, xml is still the original formula. Without MVC, we have a bloated Controller. Instead, we have a clearer hierarchy and more uniform responsibilities. Of course, there must be disadvantages behind the advantages, I believe that those who have used MVPs know what are the disadvantages, that is, the definition of interfaces will surge.

So what is the MVP model?

M is Model, what to show? That is, the data displayed on the UI, as to how the data comes, databases, networks, and other channels, all belong to this layer.

V is View, how to show? That is, how to display data. In Android, this view is usually defined using xml, and the View usually holds a Presenter reference.

P is the Presenter. Presenter plays the role of an intermediate contact, just like the Controller in MVC. Generally, Presenetr generally holds a reference of View and Model.

The links between the three are shown in:

So the question is, how can we implement the MVP model? Here is an open source library Mosby, github address https://github.com/sockeqwe/mosby

This article does not analyze the specific implementation of the library. If you are interested in the implementation, you can read the source code. After all, there is no secret before the source code. Add the dependency on the database before use.

dependencies {    compile 'com.hannesdorfmann.mosby:mvp:2.0.1'    compile 'com.hannesdorfmann.mosby:viewstate:2.0.1'}

Now let's assume that we implement a login function. The original MVC method is to define xml first, and then directly write various business logic in the Activity. As a result, the Activity becomes larger and larger. After using MVP, activity looks very clean.

The definition of XML will not be pasted here. There are two input boxes (account and password) and one login button.

First, we need an interface to interact with the server. For the sake of simplicity, we simulate it locally. If the account password is admin, the login is successful. If the account password is server, in other cases, an incorrect account or password is returned. Theoretically, this request needs to be initiated in the Child thread, and then called back through the UI thread. This step is also omitted. It is directly judged and called back in the main thread. Because it is a local simulation, no freezing occurs, in actual use, the main thread callback must be requested strictly according to the subthread.

public interface Listener
  
    {    void onSuccess(T t);    void onFailure(int code);}
  
public class LoginApi {    public static void login(String username, String password, Listener
  
    listener) {        if (username.equals("admin") && password.equals("admin")) {            listener.onSuccess(null);        } else if (username.equals("server") && password.equals("server")) {            listener.onFailure(LoginView.SERVER_ERROR);        } else {            listener.onFailure(LoginView.USERNAME_OR_PASSWORD_ERROR);        }    }}
  

The interface of business logic is defined. This LoginApi can be considered as a Model layer. Next we need to define the View and Presenter related to Login.

First, define a LoginView interface to inherit the MvpView interface. Because there are two login interfaces, one is logon success, the other is logon failure, and the other is logon failure, therefore, you need to use a status code to differentiate them. Therefore, interfaces in LoginView are generated. Here we directly define various error states in LoginView. We recommend that you define them in a constant class for unified management in actual use.

public interface LoginView extends MvpView {    public static final int USERNAME_OR_PASSWORD_EMPTY = 0x01;    public static final int USERNAME_OR_PASSWORD_ERROR = 0x02;    public static final int SERVER_ERROR = 0x03;    void onLoginSuccess();    void onLoginFailure(int code);}

Define a LoginPresenter class that inherits MvpBasePresenter. The generic parameter is LoginView. Call the LoginApi interface and return the interface.

public class LoginPresenter extends MvpBasePresenter
  
    {    public void login(final String username, final String password) {        if (username == null || username.equals("")) {            LoginView view = getView();            if (view != null) {                view.onLoginFailure(LoginView.USERNAME_OR_PASSWORD_EMPTY);                return;            }        } else if (password == null || password.equals("")) {            LoginView view = getView();            if (view != null) {                view.onLoginFailure(LoginView.USERNAME_OR_PASSWORD_EMPTY);                return;            }        }        Listener
   
     listener = new Listener
    
     () {            @Override            public void onSuccess(String str) {                LoginView view = getView();                if (view != null) {                    view.onLoginSuccess();                }            }            @Override            public void onFailure(int code) {                if (code == LoginView.USERNAME_OR_PASSWORD_ERROR) {                    LoginView view = getView();                    if (view != null) {                        view.onLoginFailure(LoginView.USERNAME_OR_PASSWORD_ERROR);                    }                } else {                    LoginView view = getView();                    if (view != null) {                        view.onLoginFailure(LoginView.SERVER_ERROR);                    }                }            }        };        LoginApi.login(username, password, listener);    }}
    
   
  

Finally, let the Activity implement the LoginView interface and implement the interface defined in LoginView. In addition, MvpActivity must be inherited. The generic parameters are LoginView and LoginPresenter, and the abstract method createPresenter () is implemented to return LoginPresenter, the two interfaces onLoginSuccess and onLoginFailure defined in LoginView are all UI-related code. There is no business logic code in the entire Activity, and the responsibility is single.

Public class LoginActivity extends MvpActivity
  
   
Implements View. onClickListener, LoginView {private EditText etAccount; private EditText etPassword; private Button btnLogin; @ Override protected void onCreate (Bundle savedInstanceState) {super. onCreate (savedInstanceState); setContentView (R. layout. activity_main); etAccount = (EditText) findViewById (R. id. accout); etPassword = (EditText) findViewById (R. id. password); btnLogin = (Button) findViewById (R. id. login); btnLogin. setOnClickListener (this) ;}@ NonNull @ Override public LoginPresenter createPresenter () {return new LoginPresenter () ;}@ Override public void onClick (View v) {switch (v. getId () {case R. id. login: onLogin (); break;} private void onLogin () {String username = etAccount. getText (). toString (); String passowrd = etPassword. getText (). toString (); getPresenter (). login (username, passowrd) ;}@ Override public void onLoginSuccess () {Toast. makeText (this, "Login successful", Toast. LENGTH_SHORT ). show () ;}@ Override public void onLoginFailure (int code) {switch (code) {case LoginView. USERNAME_OR_PASSWORD_EMPTY: Toast. makeText (this, "account or password cannot be blank", Toast. LENGTH_SHORT ). show (); break; case LoginView. USERNAME_OR_PASSWORD_ERROR: Toast. makeText (this, "incorrect account or password", Toast. LENGTH_SHORT ). show (); break; case LoginView. SERVER_ERROR: Toast. makeText (this, "server error", Toast. LENGTH_SHORT ). show (); break ;}}}
  

Note that When referencing a View in the Presenter, you must determine whether the View is not empty. Because this View is a weak reference of WeakReference, a null pointer exception will occur if you do not judge it. This is a bad part of the framework, and it needs to be repeatedly determined.

The above is the most basic usage of this framework. In actual use, we generally do not directly use its class. In general, we will define various Base classes, such as BaseView, BasePresenter, BaseActivity, and BaseFragment; so as to put all kinds of public methods in it to reduce redundancy. If you want to reference this framework, pay attention to this problem in actual use.

In addition, Mosby also has an LCE module. What is the LCE module? In fact, it is the full name of Loading-Content-Error. It is mainly used for Loading data and displaying lights, it is embodied in an MvpLceView interface and the specific implementation of MvpLceActivity and MvpLceFragment. The interface is defined as follows.

public interface MvpLceView
  
    extends MvpView {  /**   * Display a loading view while loading data in background.   * 
   The loading view must have the id = R.id.loadingView   *   * @param pullToRefresh true, if pull-to-refresh has been invoked loading.   */  public void showLoading(boolean pullToRefresh);  /**   * Show the content view.   *   * 
   The content view must have the id = R.id.contentView   */  public void showContent();  /**   * Show the error view.   * 
   The error view must be a TextView with the id = R.id.errorView   *   * @param e The Throwable that has caused this error   * @param pullToRefresh true, if the exception was thrown during pull-to-refresh, otherwise   * false.   */  public void showError(Throwable e, boolean pullToRefresh);  /**   * The data that should be displayed with {@link #showContent()}   */  public void setData(M data);  /**   * Load the data. Typically invokes the presenter method to load the desired data.   *
   

* Should not be called from presenter to prevent infinity loops. The method is declared * in * the views interface to add support for view state easily. *

* * @param pullToRefresh true, if triggered by a pull to refresh. Otherwise false. */ public void loadData(boolean pullToRefresh);}

Five methods are defined in this interface,

ShowLoading is used to display the animation when loading data. For example, the progress bar showError is used to display the content failed to load data. setData assigns a value to the data when the data is loaded successfully, call loadData to load data before calling showContent. This method is usually displayed when the showContent data called in Activity or Fragment is loaded successfully.

In addition, we also need to use MvpLceActivity or MvpLceFragment, and define related views in xml, such as errorView and contenView.

Let's take the following example to show a news list.

First, define the layout. In the layout, declare the errorView, loadingView, and contentView IDs.

<code class=" hljs xml"><framelayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent">    <!--{cke_protected}{C}%3C!%2D%2D%20Loading%20View%20%2D%2D%3E-->    <progressbar android:id="@+id/loadingView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:indeterminate="true">    <!--{cke_protected}{C}%3C!%2D%2D%20Content%20View%20%2D%2D%3E-->    <android.support.v4.widget.swiperefreshlayout android:id="@+id/contentView" android:layout_width="match_parent" android:layout_height="match_parent">        <android.support.v7.widget.recyclerview android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="match_parent">    </android.support.v7.widget.recyclerview></android.support.v4.widget.swiperefreshlayout>    <!--{cke_protected}{C}%3C!%2D%2D%20Error%20view%20%2D%2D%3E-->    <textview android:id="@+id/errorView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:text="error"></textview></progressbar></framelayout></code>

Define object classes and add constructor, getter, and setter Methods

public class News {    private String title;    private String desprition;    public News(String title, String desprition) {        this.title = title;        this.desprition = desprition;    }    public String getTitle() {        return title;    }    public void setTitle(String title) {        this.title = title;    }    public String getDesprition() {        return desprition;    }    public void setDesprition(String desprition) {        this.desprition = desprition;    }    @Override    public String toString() {        return "News{" +                "title='" + title + '\'' +                ", desprition='" + desprition + '\'' +                '}';    }}

Define the View layer interface, empty interface, inherit MvpLceView

public interface NewsView extends MvpLceView
  
   >{}
  

Define the Presenter layer and call the Model layer method to obtain the data source. Before using getView, call the isViewAttached () method or use getView! = Null. Otherwise, a null pointer exception is very likely to occur. In onSuccess, call setData and showContent at the view layer to display data. In onFaliure, call showError to display data loading failure.

public class NewsPresenter extends MvpBasePresenter
  
    {    public void loadNews(final boolean pullToRefresh) {        if (isViewAttached()) {            getView().showLoading(pullToRefresh);        }        Listener
   
    > listener=new Listener
    
     >() {            @Override            public void onSuccess(List
     
       news) {                if (isViewAttached()) {                    getView().setData(news);                    getView().showContent();                }            }            @Override            public void onFailure(int code) {                if (isViewAttached()) {                    getView().showError(new Exception("msg:"+code), pullToRefresh);                }            }        };        NewsApi.loadNews(pullToRefresh,listener);    }}
     
    
   
  

The interface method is also simulated here. However, in order to display the effects of loading animations, The subthread is simulated here and then switched back to the main thread, A random number is used to simulate server errors. If the random number is an odd number, a failure to obtain data is returned.

Public class NewsApi {private static Handler handler = new Handler (Looper. getMainLooper (); private static Random random = new Random (); public static void loadNews (final boolean pullToRefresh, final Listener
  
   
> Listener) {new Thread (new Runnable () {@ Override public void run () {final List
   
    
List = new ArrayList
    
     
(); News news1 = new News ("Title 1", "description 1 "); news news2 = new News ("title 2", "description Description 2"); News news3 = new News ("Title 3 ", "description 3"); News news4 = new News ("Title 4 ", "description 4"); News news5 = new News ("Title 5 ", "description 5"); News news6 = new News ("Title 6 ", "description Description 6"); list. add (news1); list. add (news2); list. add (News3); list. add (news4); list. add (news5); if (pullToRefresh) {list. add (news6);} try {Thread. sleep (3000);} catch (InterruptedException e) {e. printStackTrace ();} handler. post (new Runnable () {@ Override public void run () {if (listener! = Null) {listener. onFailure (1); int I = random. nextInt (100); if (I % 2 = 0) {listener. onSuccess (list);} else {listener. onFailure (1000 );}}}});}}). start ();}}
    
   
  

The corresponding Activity inherits MvpLceActivity and overwrites the abstract method. Theoretically, showContent and showError do not need to be rewritten. However, SwipeRefreshLayout is used here, And the loaded circle must be hidden, you need to override these two methods and call setRefreshing to set it to false; the string type returned by the getErrorMessage method is used to display it on errorView. If it is not a pull-down refresh, it is directly displayed on errorView. Otherwise, use Toast for pop-up. The setData method is to use the data source after the data is obtained successfully. For example, you can set it to the adapter and notify the data source to change. Call the method in presenter to load the loadData method.

Public class NewsActivity extends MvpLceActivity
  
   
, NewsView, NewsPresenter> implements NewsView, SwipeRefreshLayout. onRefreshListener {private RecyclerView recyclerView; private NewsAdapter adapter; @ Override protected void onCreate (Bundle savedInstanceState) {super. onCreate (savedInstanceState); setContentView (R. layout. activity_news); adapter = new NewsAdapter (); contentView. setOnRefreshListener (this); recyclerView = (RecyclerView) findViewById (R. id. recyclerView); recyclerView. setLayoutManager (new LinearLayoutManager (this); recyclerView. setAdapter (adapter); loadData (false) ;}@ NonNull @ Override public NewsPresenter createPresenter () {return new NewsPresenter () ;}@ Override public void showContent () {super. showContent (); contentView. setRefreshing (false) ;}@ Override public void showError (Throwable e, boolean pullToRefresh) {super. showError (e, pullToRefresh); contentView. setRefreshing (false) ;}@ Override protected String getErrorMessage (Throwable e, boolean pullToRefresh) {return "error occurred" ;}@ Override public void setData (List
   
    
Data) {adapter. setNews (data); adapter. notifyDataSetChanged () ;}@ Override public void loadData (boolean pullToRefresh) {presenter. loadNews (pullToRefresh);} @ Override public void onRefresh () {contentView. setRefreshing (true); loadData (true );}}
   
  

The adapter will not be pasted, which is relatively simple.

The final effect is as follows:

Source code.

Finally, paste all the code.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.