Android Jetpack Architecture - Paging custom pull-up loading more

Posted by ganlal on Wed, 06 May 2020 06:04:28 +0200


In the previous chapter, I introduced the basic use of Paging in Jetpack. Before reading this article, if you don't know the basic use of Paging, you can check my previous articles Android Jetpack architecture component - Paging introduction and Practice

Knowing the basic use of Paging, but it does not meet the actual development. Although Paging can realize Paging loading, when Paging requests data, as long as the data returned once is empty and PagedList is empty, Paging will not be performed again

This is obviously unfriendly, because there are many reasons why the returned data is empty, such as network or query data format, etc., and the returned PageList is empty. If the paging is ended at this time, it is obviously unacceptable;

Or the Paging loading implemented by Paging, if it slides quickly, will show the effect of loading the graphics card, and there is no friendly UI effect, as shown in the following figure:

In actual development, we hope that Paging can help us deal with Paging logic when it is sliding slowly, while when it is sliding quickly, we take over Paging loading logic ourselves, and more loading will occur, as shown in the following effect:

Next, according to the above requirements, Paging helps us page when we are normal and slowly active. When we are fast sliding, we take over the page loading of Paging,

In the example of Jetpack
ViewModel,DataSource,Paging,PagingListAdapter And cooperate SmartRefreshLayout To complete pull-up loading and pull-down refresh

  • Of course, there are many ways to listen to RecycleView to load more views. Here you can directly use SmartRefreshLayout

Before you start, use ViewModel+DataSource+PagingListAdapter to bind data to RecycleView. If you have seen the previous Basic use , the following basic usage parts can be skipped

Basic use of Paging

  • 1. After the basic usage and data loading of Paging are completed, the code in Activity is as follows:
package com.onexzgj.inspur.pageingsample.pagingpro;

public class PagingProActivity extends AppCompatActivity implements OnRefreshListener, OnLoadMoreListener {

    @SuppressLint("RestrictedApi")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_paging_pro);

        recyclerView.setLayoutManager(new LinearLayoutManager(this,LinearLayoutManager.VERTICAL,false));

        adapter = new PagingProAdapter(this);
        recyclerView.setAdapter(adapter);

        paingProViewModel = new ViewModelProvider.NewInstanceFactory().create(PaingProViewModel.class);

        paingProViewModel.getPageData().observe(this, new Observer<PagedList<ResponseArticle.DataBean.Article>>() {
            @Override
            public void onChanged(PagedList<ResponseArticle.DataBean.Article> articles) {
                submitList(articles);
            }
        });
    }

    public void submitList(PagedList<ResponseArticle.DataBean.Article> result) {
        if (result.size() > 0) {
            adapter.submitList(result);
        }
    }
}
  • 2. Let's take a look at the implementation in PaingProViewModel
package com.onexzgj.inspur.pageingsample.pagingpro;
/**
 * author: onexzgj
 * time: 2020/5/4
 */
public class PaingProViewModel extends AbsPagingProViewModel<ResponseArticle.DataBean.Article> {
    private AtomicBoolean loadAfter = new AtomicBoolean(false);
    private int mPageIndex = 0;

    public int getmPageIndex() {
        return mPageIndex;
    }

    @Override
    protected DataSource createDataSource() {
        return new ArticleDataSource();
    }


    class ArticleDataSource extends PageKeyedDataSource<Integer, ResponseArticle.DataBean.Article> {

        @Override
        public void loadInitial(@NonNull LoadInitialParams<Integer> params, @NonNull LoadInitialCallback<Integer, ResponseArticle.DataBean.Article> callback) {
            loadData(0, callback, null);
        }

        @Override
        public void loadBefore(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Integer, ResponseArticle.DataBean.Article> callback) {
            callback.onResult(Collections.emptyList(), 0);
        }

        @Override
        public void loadAfter(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Integer, ResponseArticle.DataBean.Article> callback) {
            loadData(params.key, null, callback);
        }
    }


    //Simple request network business logic
    @SuppressLint("RestrictedApi")
    private void loadData(int pageIndex, PageKeyedDataSource.LoadInitialCallback<Integer, ResponseArticle.DataBean.Article> initCallback, PageKeyedDataSource.LoadCallback<Integer, ResponseArticle.DataBean.Article> callback) {

        mPageIndex = pageIndex;
        if (pageIndex > 0) {
            loadAfter.set(true);
        }

        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder().url("https://www.wanandroid.com/article/list/" + pageIndex + "/json").build();
        try {
            Response response = null;
            response = client.newCall(request).execute();
            if (response.isSuccessful()) {
                ResponseArticle responseArticle = JSON.parseObject(response.body().string(), ResponseArticle.class);

                if (initCallback != null) {
                    initCallback.onResult(responseArticle.getData().getDatas(), pageIndex - 1, pageIndex + 1);
                } else {
                    callback.onResult(responseArticle.getData().getDatas(), pageIndex + 1);
                }

                if (pageIndex > 0) {
                    //Send data through BoundaryPageData to tell the UI layer whether it should actively turn off the animation of pull-up loading pages
                    ((MutableLiveData) getBoundaryPageData()).postValue(responseArticle.getData().getDatas().size() > 0);
                    loadAfter.set(false);
                }
                mPageIndex = pageIndex + 1;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • 3. Data binding to RecycleView via PagedListAdapter
import com.onexzgj.inspur.pageingsample.R;
/**
 * author: onexzgj
 * time: 2020/5/4
 */
public class PagingProAdapter extends PagedListAdapter<ResponseArticle.DataBean.Article, PagingProAdapter.ViewHolder> {
    public Context mContext;

    protected PagingProAdapter(Context context) {
        super(new DiffUtil.ItemCallback<ResponseArticle.DataBean.Article>() {

            @Override
            public boolean areItemsTheSame(@NonNull ResponseArticle.DataBean.Article oldItem, @NonNull ResponseArticle.DataBean.Article newItem) {
                return oldItem == newItem;
            }

            @Override
            public boolean areContentsTheSame(@NonNull ResponseArticle.DataBean.Article oldItem, @NonNull ResponseArticle.DataBean.Article newItem) {
                return oldItem.getId() == newItem.getId();
            }
        });
        this.mContext= context;
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View itemView = LayoutInflater.from(mContext).inflate(R.layout.item, parent, false);
        return new ViewHolder(itemView);
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        holder.bindData(getItem(position));
    }

    class ViewHolder extends RecyclerView.ViewHolder {
        private TextView nameView;

        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            nameView = itemView.findViewById(R.id.tv_info);
        }

        public void bindData(ResponseArticle.DataBean.Article item) {
            nameView.setText(item.getTitle());
        }
    }
}

The basic use of Paging has been completed. Next, we will manually take over the pull-up loading and down refreshing of Paging

Implement pull-up loading and pull-down refresh

  • 1. Through SmartRefreshLayout , to monitor the pull-down refresh and pull-up load of RecycleView. How to use SmartRefreshLayout is not detailed here
        ...
        smartRefreshLayout.setEnableRefresh(true);
        smartRefreshLayout.setEnableLoadMore(true);
        smartRefreshLayout.setOnRefreshListener(this);
        smartRefreshLayout.setOnLoadMoreListener(this);
        ...
  • 2. Refresh logic implementation
    By implementing onRefresh() of smartRefreshLayout, reinitialize the DataSource as follows:
    @Override
    public void onRefresh(@NonNull RefreshLayout refreshLayout) {
        paingProViewModel.getDataSource().invalidate();
    }
  • 3. Load more logical implementations

The implementation logic in loadMore() of smartRefreshLayout is implemented as follows:

    @Override
    public void onLoadMore(@NonNull RefreshLayout refreshLayout) {

        //If the list data is empty, pull-up will not be triggered to load more data
        final PagedList<ResponseArticle.DataBean.Article> currentList = adapter.getCurrentList();
        if (currentList == null || currentList.size() <= 0) {
            finishRefresh(false);
            return;
        }

        //It should be noted that in PaingProViewModel, the loadAfter method is implemented to realize the logic of paging data request
        paingProViewModel.loadAfter(paingProViewModel.getmPageIndex(),new PageKeyedDataSource.LoadCallback<Integer, ResponseArticle.DataBean.Article>(){

            @Override
            public void onResult(@NonNull List<ResponseArticle.DataBean.Article> data, @Nullable Integer adjacentPageKey) {
                PagedList.Config config = currentList.getConfig();
                if (data != null && data.size() > 0) {
                    //Here we can also use mutableitemkeyedatasource when we manually take over paging data loading.
                    //Because we take over when and only when paging doesn't page us anymore. So we don't need the DataSource created in ViewModel to continue working, so we use the new DataSource object, MutablePageKeyedDataSource
                    MutablePageKeyedDataSource dataSource = new MutablePageKeyedDataSource();

                    //Here, you need to add what is already shown in the list to dataSource.data first
                    //Then add the data from this paging back to dataSource.data
                    dataSource.data.addAll(currentList);
                    dataSource.data.addAll(data);

                    PagedList pagedList = dataSource.buildNewPagedList(config);
                    submitList(pagedList);
                }
            }
        });
    }

You can see that we can take over the request data logic of Paging by defining the loadAfter method in the PaingProViewModel,

  • 4. Implement the custom method loadAfter() in PaingProViewModel
    @SuppressLint("RestrictedApi")
    public void loadAfter(int pageIndex, PageKeyedDataSource.LoadCallback<Integer, ResponseArticle.DataBean.Article> callback) {

        Log.d("TAG", "loadAfter: pageIndex" + pageIndex);
        //Whether to load more representation bits
        if (loadAfter.get()) {
            callback.onResult(Collections.emptyList(), 0);
            return;
        }

        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder().url("https://www.wanandroid.com/article/list/" + pageIndex + "/json").build();
        ArchTaskExecutor.getIOThreadExecutor().
                execute(new Runnable() {
                            @Override
                            public void run() {
                                try {
                                    Response response = null;
                                    response = client.newCall(request).execute();
                                    if (response.isSuccessful()) {
                                        ResponseArticle responseArticle = JSON.parseObject(response.body().string(), ResponseArticle.class);
                                        callback.onResult(responseArticle.getData().getDatas(), pageIndex + 1);

                                        if (pageIndex > 0) {
                                            //Send data through BoundaryPageData to tell the UI layer whether it should actively turn off the animation of pull-up loading pages
                                            ((MutableLiveData) getBoundaryPageData()).postValue(responseArticle.getData().getDatas().size() > 0);
                                            loadAfter.set(false);
                                        }
                                        mPageIndex = pageIndex + 1;
                                    }
                                } catch (IOException e) {
                                    e.printStackTrace();
                                }
                            }
                        }
                );
    }

loadAfter is to set whether it is the flag bit of Paging pull-up loading. Pull-up loading is only taken over when Paging has done pull-up loading, that is, when the loaded page number is greater than 0, it is taken over. Otherwise, an empty PagedList is returned.

  • 5. Implementation of custom MutablePageKeyedDataSource
package com.onexzgj.inspur.pageingsample.pagingpro;

@SuppressLint("RestrictedApi")
public class MutablePageKeyedDataSource<Value> extends PageKeyedDataSource<Integer, Value> {
    public List<Value> data = new ArrayList<>();

    public PagedList<Value> buildNewPagedList(PagedList.Config config) {
      PagedList<Value> pagedList = new PagedList.Builder<Integer, Value>(this, config)
                .setFetchExecutor(ArchTaskExecutor.getIOThreadExecutor())
                .setNotifyExecutor(ArchTaskExecutor.getMainThreadExecutor())
                .build();

        return pagedList;
    }

    @Override
    public void loadInitial(@NonNull LoadInitialParams<Integer> params, @NonNull LoadInitialCallback<Integer, Value> callback) {
        callback.onResult(data, null, null);
    }

    @Override
    public void loadBefore(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Integer, Value> callback) {
        callback.onResult(Collections.emptyList(), null);
    }

    @Override
    public void loadAfter(@NonNull LoadParams<Integer> params, @NonNull LoadCallback<Integer, Value> callback) {
        callback.onResult(Collections.emptyList(), null);
    }
}

It is equivalent to re creating a new DataSource, and binding the data collection to build a PagedList object for Paging to use.

summary

Here, more introduction of Paging custom pull-up loading is finished, and the summary of file creation is made. That is to listen to the loadMore method of RecycleView through SmartRefreshLayout, load data by customizing loadAfter in ViewModel, recreate DataSource, List collection data, and rebuild a PageList. The sample code in this article has been listed Jetpack/pagingpro

This warehouse is a warehouse to demonstrate the components of Jetpack. It introduces and uses Lifecyele, LiveData, ViewModel, Room, WorkManager and Paging respectively

##Detailed introduction

The project directory structure is as follows

Topics: Android JSON Database network