使用Mobsy進行MVP實戰

MVP介紹

  • M:Model,需要顯示的數據,以及獲取和保存數據的相關邏輯
  • V:View,顯示數據的頁面或空間,并接受用戶的交互
  • P:Presenter,處于M和V中間,是對產品交互的抽象。決定M由哪個V顯示,V的動作會引起哪些數據的變化。

如下為典型的MVP工作流程

mvp-workflow.png

需要注意的點:

  1. Presenter不應該直接處理View的事件
  2. View只應向Presenter傳遞消息,并接受Presenter的命令
  3. Activity和Fragment是View的一部分,一般可用于處理用戶事件
  4. Presenter和Model應該是純Java代碼,而且可以獨立的運行單元測試

Mosby介紹

gradle依賴

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

MvpView和MvpPresenter

  • MvpView是個空接口。在實際使用時,會擴展這個接口來定義一系列的View的方法
  • MvpView會依附或脫離于MvpPresenter。庫中定義好的一些MvpView使用代理模式實現了依附和脫離的邏輯。
  • MvpPresenter通過軟引用訪問View,從而避免內存泄漏
public interface MvpView { }


public interface MvpPresenter<V extends MvpView> {

  public void attachView(V view);

  public void detachView(boolean retainInstance);
}

通過MvpLceFragment學習使用MVP

LCE就是Loading-Content-Error,代表了一個典型的移動互聯網應用的頁面。

  1. 顯示LoadingView,并在后臺獲取數據
  2. 如果獲取成功,顯示獲取的到數據
  3. 如果失敗,顯示一個錯誤的提示View

先看看MvpLceView

/**
 * @param <M> The type of the data displayed in this view
 */
public interface MvpLceView<M> extends MvpView {

  /**
   * Display a loading view while loading data in background.
   * <b>The loading view must have the id = R.id.loadingView</b>
   *
   * @param pullToRefresh true, if pull-to-refresh has been invoked loading.
   */
  public void showLoading(boolean pullToRefresh);

  /**
   * Show the content view.
   *
   * <b>The content view must have the id = R.id.contentView</b>
   */
  public void showContent();

  /**
   * Show the error view.
   * <b>The error view must be a TextView with the id = R.id.errorView</b>
   *
   * @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);
}

實現MvpLceView的控件或頁面一定要包含至少3個View,他們的id分別為R.id.loadingView,R.id.contentViewR.id.errorView,因此我們使用如下的xml為Fragment布局。

庫工程中的MvpLceFragmentMvpLceActivity已經實現了MvpLceView的三個方法
showLoadingshowContentshowError

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >

  <!-- Loading View -->
  <ProgressBar
    android:id="@+id/loadingView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:indeterminate="true"
    />

  <!-- Content View -->
  <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.v4.widget.SwipeRefreshLayout>


    <!-- Error view -->
    <TextView
      android:id="@+id/errorView"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      />

</FrameLayout>

這個頁面中將顯示一個從網絡獲取的國家列表,先看看Presenter的代碼。這里通過CountriesAsyncLoader獲取國家列表,并通過setData和showContent讓View顯示這些國家信息。當然還獲取前顯示Loading,獲取失敗后顯示Error。

public class CountriesPresenter extends MvpBasePresenter<CountriesView> {

  @Override
  public void loadCountries(final boolean pullToRefresh) {

    getView().showLoading(pullToRefresh);


    CountriesAsyncLoader countriesLoader = new CountriesAsyncLoader(
        new CountriesAsyncLoader.CountriesLoaderListener() {

          @Override public void onSuccess(List<Country> countries) {

            if (isViewAttached()) {
              getView().setData(countries);
              getView().showContent();
            }
          }

          @Override public void onError(Exception e) {

            if (isViewAttached()) {
              getView().showError(e, pullToRefresh);
            }
          }
        });

    countriesLoader.execute();
  }
}

最后是MvpLceFragment,注意其中的createPresenter是所有的MvpView都需要實現的方法,用于創建和MvpView關聯的Presenter,另一個setData兩個方法是MvpLceFragment中沒有實現的方法,因為只有實現的時候才知道最終的Model,已經如何顯示這個Model。

另一個要注意的是MvpLceFragment的四個范型,依次是:顯示內容的AndroidView,需要顯示的內容Model,MvpView,MvpPresenter。

public class CountriesFragment
    extends MvpLceFragment<SwipeRefreshLayout, List<Country>, CountriesView, CountriesPresenter>
    implements CountriesView, SwipeRefreshLayout.OnRefreshListener {

  @Bind(R.id.recyclerView) RecyclerView recyclerView;
  CountriesAdapter adapter;

  @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    return inflater.inflate(R.layout.countries_list, container, false);
  }

  @Override public void onViewCreated(View view, @Nullable Bundle savedInstance) {
    super.onViewCreated(view, savedInstance);

    // Setup contentView == SwipeRefreshView
    contentView.setOnRefreshListener(this);

    // Setup recycler view
    adapter = new CountriesAdapter(getActivity());
    recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
    recyclerView.setAdapter(adapter);
    loadData(false);
  }

  public void loadData(boolean pullToRefresh) {
    presenter.loadCountries(pullToRefresh);
  }

  @Override protected CountriesPresenter createPresenter() {
    return new SimpleCountriesPresenter();
  }

  @Override public void setData(List<Country> data) {
    adapter.setCountries(data);
    adapter.notifyDataSetChanged();
  }

  @Override public void onRefresh() {
    loadData(true);
  }
}

實戰——封裝一個LceListView

這部分代碼可參考代碼,只用關注mvplist包下的相關代碼即可。

這個頁面的View結構和之前的MvpLceFrgment類似,并通過修改Adapter給RecycleView的末尾增加了一個LoadMoreView。將這類業務的下拉刷新,上拉加載更多,以及錯誤處理都抽象出來。實現列表時,剩下的邏輯主要包括

  1. Model:獲取的數據,以及從數據中獲取每個列表項展示需要的數據列表
  2. Presenter:刷新和加載更多時,分別調用Model的獲取數據方法
  3. View:根據數據決定ViewHolder的類型,以及ViewHolder的實現

如何使用,可以參考mvplist.sample包的內容

基類LecListView的方案

LecListView包含了一個LoadMoreView,LoadMoreView也符合Mvp架構。

我們先看看LoadMoreView的實現。

LoadMoreView

  • 沒有Model:這里的數據只包括一個狀態,因此沒有對應的Model類
  • LoadMoreView
    • 提供一個setState(int state)方法,供Presenter更新狀態
    • 加載更多失敗的情況下,點擊View會請求Presenter更改狀態到Loading
  • LoadMorePresenter
    • 通過setLoadMoreState(int state)改變View的狀況
    • 通過接口LoadMoreListener#onLoadMore通知LceListViewPresenter加載更多數據

LecListView

  • IListModel:在LceListView中,Model應實現IListModel,從而提供一個在RecycleView中顯示的數據列表。
```Java
public interface IListModel<M> {
    List<M> getData();
}
``` 
  • LceListView
    • 實現了MvpLceView的五個方法,和在列表底部添加數據的addData方法
    • 監聽RecyclerView的滾動,通知Presenter改變加載更多的狀態
    • 使用LceListAdapter為RecyclerView底部增加了LoadMoreView
  • LceListPresenter實現ILceListPresenter
    • 在refreshData時,通知LceListView顯示Loading
    • 包含一個LoadMorePresenter來實現ILoadMorePresenter的接口
    • 在LoadMoreView附著在窗口時,調用LceListPresenter#setLoadMorePresenter

參考文章

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容