A brief introduction to MVP's practical exercises to make the code structure simpler~

A brief introduction to MVP's practical exercises to make the code structure simpler~

Preface

To be honest, I haven't updated my blog for a long time. The main reason is that the framework issues I have been maintaining have been endless, and answering questions in the technical exchange group has also exhausted me physically and mentally. So I would like to say sorry to those who follow me here for not bringing you benefits regularly. So here is a big benefit for everyone: Ai Ya Mei Paper - a complete App of Retrofit & RxJava & MVP & Butterknife.

Speaking of the answers to the questions that have exhausted me recently, it is undoubtedly what has made me go further and further on the road of open source. Although I am not a technical expert, I am still bombarded with some very simple questions. In fact, the author really refuses to answer them in his heart. I have to say, I write to you and the group owner in the technical group, why do you ask questions, but no one always replies to you! It is well written.

Overview

Without further ado, I believe most people can say something about MVP (Model View Presenter), "an evolved version of MVC", "completely decoupling Model and View", etc., but those who have used MVP will definitely feel that in Android, the code is very clear, but there are too many classes. For most people, when watching the MVP Demo, it is nice at first glance, but when they are asked to write an example, they have a headache and can't write it. But it is true that the MVC model is more like a smooth sailing. Just put your business logic into the Activity and it will be done successfully.

I have to say that we did use MVC to write our previous projects. It is easy to find that any Activity code is hundreds or thousands of lines, and even more than 10,000 lines. It does look like that, but if you think about it carefully, there is actually very little that the View can do for the layout file. In fact, the data binding operations and event handling operations in the layout file are all in the Activity, making the Activity look like both a View and a Controller. Apart from the ugly appearance of the code, it is really hard to read the code later.

Don't believe it? Take a look.

Maybe MVC is OK for functions with simple business logic, but have you ever thought about what to do if your product requirements change later? Yes, you accept the rape of product requirements, but you still have to endure the humiliation. With increasingly complex business logic, your Activity and Fragment codes are increasing, which eventually leads to code explosion and difficulty in maintenance.

After browsing the Internet, I found that there are many articles about MVP, which shows the popularity of MVP. However, most of the articles only talk about theory, and the better ones will come with a simple login demo. However, a simple demo is difficult for people who are new to the MVP mode to master its use. So AiAMeiPie came into being.

What is MVP

Of course, we cannot go off topic. We have given a brief overview of MVP above, so let’s use a simple diagram to illustrate it below.

As shown in the figure above, in the project, View and Model do not interact directly, but use Presenter as a bridge between View and Model. The Presenter holds references to the Interface of both the View layer and the Model layer, and the View layer holds references to the Interface of the Presenter layer. When a page of the View layer needs to display certain data, it will first call a certain interface of the Presenter layer, and then the Presenter layer will call the Model layer to request data. When the Model layer data is loaded successfully, it will call the callback method of the Presenter layer to notify the Presenter layer that the data loading is complete. Finally, the Presenter layer calls the View layer interface to display the loaded data to the user. This is the core process of the MVP mode.

The advantage of this layering is that it greatly reduces the coupling between the Model and View layers. On the one hand, the View layer and the Model layer can be developed and tested separately without relying on each other. On the other hand, the Model layer can be encapsulated and reused, which can greatly reduce the amount of code. Of course, MVP has some other advantages, which will not be repeated here.

Function Display

Here I will just show you the functions of the useful information section.

The layout is fairly simple.

  1. <android.support.v4.widget.SwipeRefreshLayout
  2. xmlns:android= "http://schemas.android.com/apk/res/android"  
  3. xmlns:app= "http://schemas.android.com/apk/res-auto"  
  4. android:id= "@+id/swipe_refresh_layout"  
  5. android:layout_width= "match_parent"  
  6. android:layout_height= "match_parent" >
  7.  
  8. <com.nanchen.aiyagirl.widget.RecyclerViewWithFooter.RecyclerViewWithFooter
  9. android:id= "@+id/recyclerView"  
  10. android:layout_width= "match_parent"  
  11. android:layout_height= "match_parent" />
  12.  
  13. </android.support.v4.widget.SwipeRefreshLayout>

The dry goods module is a Fragment, which contains a RecyclerView that supports pull-down refresh and pull-up to load data. So our Presenter and View only need to define simple methods.

1) Display the loading progress bar during the data loading process;

2) After the data is loaded successfully, the Adapter is reminded to refresh the data;

3) Loading failure window reminds users of relevant information;

4) Hide the progress bar when loading is finished;

  1. public interface CategoryContract {
  2. interface ICategoryView extends BaseView{
  3. void getCategoryItemsFail(String failMessage);
  4. void setCategoryItems(CategoryResult categoryResult);
  5. void addCategoryItems(CategoryResult categoryResult);
  6. void showSwipeLoading();
  7. void hideSwipeLoading();
  8. void setLoading();
  9. String getCategoryName();
  10. void noMore();
  11. } interface ICategoryPresenter extends BasePresenter{
  12. void getCategoryItems(boolean isRefresh);
  13. }
  14. }

Write the Presenter implementation class.

  1. public class CategoryPresenter implements ICategoryPresenter {
  2. private ICategoryView mCategoryICategoryView;
  3. private int mPage = 1;
  4. private Subscription mSubscription;
  5. public CategoryPresenter(ICategoryView androidICategoryView) {
  6. mCategoryICategoryView = androidICategoryView;
  7. }
  8. @Override
  9. public void subscribe() {
  10. getCategoryItems( true );
  11. }
  12. @Override
  13. public void unSubscribe() {
  14. if (mSubscription != null && !mSubscription.isUnsubscribed()){
  15. mSubscription.unsubscribe();
  16. }
  17. } @Override
  18. public void getCategoryItems(final boolean isRefresh) {
  19. if (isRefresh) {
  20. mPage = 1;
  21. mCategoryICategoryView.showSwipeLoading();
  22. } else {
  23. mPage++;
  24. }
  25. mSubscription = NetWork.getGankApi()
  26. .getCategoryData(mCategoryICategoryView.getCategoryName(), GlobalConfig.CATEGORY_COUNT,mPage)
  27. .subscribeOn(Schedulers.io())
  28. .observeOn(AndroidSchedulers.mainThread())
  29. .subscribe(new Observer<CategoryResult>() {
  30. @Override
  31. public void onCompleted() {
  32.  
  33. }
  34. @Override
  35. public void onError(Throwable e) {
  36. mCategoryICategoryView.hideSwipeLoading();
  37. mCategoryICategoryView.getCategoryItemsFail(mCategoryICategoryView.getCategoryName()+ "Failed to obtain list data!" );
  38. }
  39. @Override
  40. public void onNext(CategoryResult categoryResult) {
  41. if (isRefresh){
  42. mCategoryICategoryView.setCategoryItems(categoryResult);
  43. mCategoryICategoryView.hideSwipeLoading();
  44. mCategoryICategoryView.setLoading();
  45. } else {
  46. mCategoryICategoryView.addCategoryItems(categoryResult);
  47. }
  48. }
  49. });
  50. }
  51. }

Write an Adapter to display data.

  1. class CategoryRecyclerAdapter extends CommonRecyclerAdapter<CategoryResult.ResultsBean> implements
  2. ListenerWithPosition.OnClickWithPositionListener<CommonRecyclerHolder>{
  3.  
  4. CategoryRecyclerAdapter(Context context) {
  5. super(context, null , R.layout.item_category);
  6. } @Override
  7. public void convert (CommonRecyclerHolder holder, ResultsBean resultsBean) {
  8. if (resultsBean != null ) {
  9. ImageView imageView = holder.getView(R.id.category_item_img);
  10. if (ConfigManage.INSTANCE.isListShowImg()) { // List display pictures
  11. imageView.setVisibility( View .VISIBLE);
  12. String quality = "" ;
  13. if (resultsBean.images != null && resultsBean.images. size () > 0) {
  14. switch (ConfigManage.INSTANCE.getThumbnailQuality()) {
  15. case 0: // original image
  16. quality = "" ;
  17. break;
  18. case 1: //
  19. quality = "?imageView2/0/w/400" ;
  20. break;
  21. case 2:
  22. quality = "?imageView2/0/w/190" ;
  23. break;
  24. }
  25. Glide. with (mContext)
  26. . load (resultsBean.images.get(0) + quality)
  27. .placeholder(R.mipmap.image_default)
  28. .error(R.mipmap.image_default)
  29. . into (imageView);
  30. } else { // The list does not display pictures
  31. Glide. with (mContext). load (R.mipmap.image_default). into (imageView);
  32. }
  33. } else {
  34. imageView.setVisibility( View .GONE);
  35. }
  36.  
  37. holder.setTextViewText(R.id.category_item_desc, resultsBean. desc == null ? "unknown" : resultsBean. desc );
  38. holder.setTextViewText(R.id.category_item_author, resultsBean.who == null ? "unknown" : resultsBean.who);
  39. holder.setTextViewText(R.id.category_item_time, TimeUtil.dateFormat(resultsBean.publishedAt));
  40. holder.setTextViewText(R.id.category_item_src, resultsBean.source == null ? "unknown" : resultsBean.source);
  41. holder.setOnClickListener(this, R.id.category_item_layout);
  42. }
  43. } @Override
  44. public void onClick( View v, int position, CommonRecyclerHolder holder) {
  45. //Toasty.info(mContext, "Jump to the corresponding web page!" , Toast.LENGTH_SHORT, true ).show();
  46. Intent intent = new Intent(mContext, WebViewActivity.class);
  47. intent.putExtra(WebViewActivity.GANK_TITLE, mData.get(position) .desc );
  48. intent.putExtra(WebViewActivity.GANK_URL, mData.get(position).url);
  49. mContext.startActivity(intent);
  50. }
  51. }

***Of course it’s Fragment.

  1. public class CategoryFragment extends BaseFragment implements ICategoryView, OnRefreshListener, OnLoadMoreListener {
  2. public   static final String CATEGORY_NAME = "com.nanchen.aiyagirl.module.category.CategoryFragment.CATEGORY_NAME" ;
  3. @BindView(R.id.recyclerView)
  4. RecyclerViewWithFooter mRecyclerView;
  5. @BindView(R.id.swipe_refresh_layout)
  6. SwipeRefreshLayout mSwipeRefreshLayout;
  7. private String categoryName;
  8. private CategoryRecyclerAdapter mAdapter;
  9. private ICategoryPresenter mICategoryPresenter;
  10. public   static CategoryFragment newInstance(String mCategoryName) {
  11. CategoryFragment categoryFragment = new CategoryFragment();
  12. Bundle bundle = new Bundle();
  13. bundle.putString(CATEGORY_NAME, mCategoryName);
  14. categoryFragment.setArguments(bundle);
  15. return categoryFragment;
  16. } @Override
  17. protected int getContentViewLayoutID() {
  18. return R.layout.fragment_category;
  19. } @Override
  20. protected void init() {
  21. mICategoryPresenter = new CategoryPresenter(this);
  22. categoryName = getArguments().getString(CATEGORY_NAME);
  23. mSwipeRefreshLayout.setOnRefreshListener(this);
  24. mAdapter = new CategoryRecyclerAdapter(getActivity());
  25. mRecyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
  26. mRecyclerView.addItemDecoration(new RecyclerViewDivider(getActivity(), LinearLayoutManager.HORIZONTAL));
  27. mRecyclerView.setAdapter(mAdapter);
  28. mRecyclerView.setOnLoadMoreListener(this);
  29. mRecyclerView.setEmpty();
  30. mICategoryPresenter.subscribe();
  31. } @Override
  32. public void onDestroy() {
  33. super.onDestroy();
  34. if (mICategoryPresenter != null ) {
  35. mICategoryPresenter.unSubscribe();
  36. }
  37. } @Override
  38. public void onRefresh() {
  39. mICategoryPresenter.getCategoryItems( true );
  40. } @Override
  41. public void onLoadMore() {
  42. mICategoryPresenter.getCategoryItems( false );
  43. } @Override
  44. public void getCategoryItemsFail(String failMessage) {
  45. if (getUserVisibleHint()) {
  46. Toasty.error(this.getContext(), failMessage).show();
  47. }
  48. } @Override
  49. public void setCategoryItems(CategoryResult categoryResult) {
  50. mAdapter.setData(categoryResult.results);
  51. } @Override
  52. public void addCategoryItems(CategoryResult categoryResult) {
  53. mAdapter.addData(categoryResult.results);
  54.  
  55. } @Override
  56. public void showSwipeLoading() {
  57. mSwipeRefreshLayout.setRefreshing( true );
  58. } @Override
  59. public void hideSwipeLoading() {
  60. mSwipeRefreshLayout.setRefreshing( false );
  61. } @Override
  62. public void setLoading() {
  63. mRecyclerView.setLoading();
  64. } @Override
  65. public String getCategoryName() {
  66. return this.categoryName;
  67. } @Override
  68. public void noMore() {
  69. mRecyclerView.setEnd( "No more data" );
  70. }
  71. }

Project screenshots

Let me show you the project screenshots to prevent you from getting nervous.

Conclusion

Aiyameizhi is a practical app that integrates mainstream frameworks such as MVP, Retrofit, and RxJava. The project resources come from the practical concentration camp of coders. The amount of code is not much, but it basically covers all aspects. The interface adopts the design style, so it is also a good medicine for learning design. The author also hopes to continue to go further and further on the road of open source, and please support it.

<<:  Android imitates Huawei's weather drawing dial

>>:  Use Node.js to segment text content and extract keywords

Recommend

Sogou promotion case in the gaming industry!

Since the outbreak of the epidemic began in early...

How to place orders and bid on Juliang Qianchuan

Before the launch of Juleliang Qianchuan, the Dou...

Is it okay to put two Wenchang Pagodas at home? Will that be too much?

I think everyone knows that the placement of the ...

Why does outdoor exercise help slow the progression of myopia?

Now we often hear that taking children to outdoor...

How can we have high-quality sleep?

"Apply the most expensive facial mask and st...

China's Sky Eye, moving towards 1,000 pulsars!

In recent days, a piece of news about China's...