[Android+JetPack] (Java实现) Retrofit2+RxJava3+Paging3+RecyclerView 实现加载网络数据例子 记录

文章目录

  • 前言
  • 参考链接
  • 依赖库及版本
  • Demo效果
  • 接口及数据展示
  • 各项模块
    • `Retrofit2`
      • Bean,对应上面的接口返回.
      • Service API部分
    • `Paging3`
      • `PagingSource`以及 `RxPagingSource`
      • `PagingDataAdapter` 适配器
      • `ViewModel`
      • `PublicInfoPage /Activity`
  • 最后

前言


继续安卓学习之旅,本章的主要目标是:

1.完成一个无限上拉加载的列表(Paging3 + RecyclerView)
2.加载的是网络数据, 要采用主流的 Retrofit+okhttp方式
3.在了解了RxJava之后,也希望用上Rxjava
4.用到ViewModel来配合,以及一些jetpack的东西都用上

(为什么不用Paging2? 这里主要是看说3比2还要方便些,所以就偷懒没去用Paging2)

参考链接

这些是在学习和搜索中看到的比较好的文章,不过他们要么是kotlin 要么是RxJava2,都不是能直接套上去就用的,但是从文章里面总结归纳,也是有借鉴效果的.

  1. SmartRefreshLayout-github 这个后期再结合Paging3,完成一个有酷炫下拉及淘宝二楼效果的的demo
  2. Java实现)使用官方Paging3分页库实现RecyclerView加载更多(loadmore)的功能 这个较为简洁,没那么多原理的描述,方便更实战的理解借鉴
  3. Android paging3 使用和踩坑经验分享 这个虽然是kotlin,不过里面一些名词的解释不错, 适合快速扫盲
  4. Jetpack新成员,Paging3从吐槽到真香

依赖库及版本

为什么要说这个, 因为在实际百度各方面资料的时候,没仔细区分好版本,导致在练习过程中走了不少弯路,踩了坑.为避免这个情况,这里列出本Demo中的各个依赖库及版本

Retrofix2

    // 引入 retrofix 网络框架(自带okhttp)
    // github :https://github.com/square/retrofit
    // 视频教学
    // https://www.bilibili.com/video/BV1vV411W75V?p=4&vd_source=3dc64571e08f84008d5c43796c009480
    implementation "com.squareup.retrofit2:retrofit:2.9.0"
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
    implementation 'com.squareup.retrofit2:adapter-rxjava3:2.9.0'

Rxjava3

    // 支持RxJava/RxAndroid
    implementation 'io.reactivex.rxjava3:rxandroid:3.0.2'
    implementation 'io.reactivex.rxjava3:rxjava:3.1.5'

Paging3

    // 引入 paging 3
    // 注意, 由于上面我们用的是 retrofit2 + rxjava3
    // 所以,在使用 paging3的时候, 要选 支持rxjava3的 paging-rxjava3
    // 切记版本对应好
    def paging_version = "3.1.1"
    implementation "androidx.paging:paging-runtime:$paging_version"
//    // optional - RxJava2 support
//    implementation "androidx.paging:paging-rxjava2:$paging_version"
    // optional - RxJava3 support
    implementation "androidx.paging:paging-rxjava3:$paging_version"

这里稍微提一下, 如果用的是RxJava3, 就使用RxJava3 support的可选项, 不然不匹配,但同时也造成另一个问题, 这里插入说一下哈

就是
包括目前官网(点击进入)那边的, 关于对RxPagingSource的示例里面, 也应该用的还是RxJava2,如果你和我一样用RxJava3,那大概率在做map的时候,会报错说, 类型转换失败, 不能用 this::toLoadResult
这个稍后再说…

[Android+JetPack] (Java实现) Retrofit2+RxJava3+Paging3+RecyclerView 实现加载网络数据例子 记录_第1张图片

Demo效果

[Android+JetPack] (Java实现) Retrofit2+RxJava3+Paging3+RecyclerView 实现加载网络数据例子 记录_第2张图片

一个简单的demo

接口及数据展示

[Android+JetPack] (Java实现) Retrofit2+RxJava3+Paging3+RecyclerView 实现加载网络数据例子 记录_第3张图片

各项模块

Retrofit2

Bean,对应上面的接口返回.

Response_public_info_bean

package retrofit.bean;

import java.util.List;

/**
 * @author: tiannan
 * @time: 2023/4/12.
 * @email: [email protected]
 * @descripetion: 此处添加描述
 */
public class Response_public_info_bean {
    private String msg;
    private String code;
    private Datas data;


    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public Datas getData() {
        return data;
    }

    public void setData(Datas data) {
        this.data = data;
    }


    public class Datas {
        private int pageSize;
        private List<Cell> list;

        public int getPageSize() {
            return pageSize;
        }

        public void setPageSize(int pageSize) {
            this.pageSize = pageSize;
        }

        public List<Cell> getList() {
            return list;
        }

        public void setList(List<Cell> list) {
            this.list = list;
        }

        @Override
        public String toString() {
            return "Datas{" +
                    "list=" + list +
                    '}';
        }

        public class Cell {
            private String productName;
            private String productTypeName;
            private String riskRateName;

            private int id;

            private int pageNum; // 增加两个下标 page页下标
            private int indexNum;// 增加两个下标 newsInfo(cell)页下标

            @Override
            public String toString() {
                return "News{" +
                        "productName='" + productName + '\'' +
                        ", productTypeName='" + productTypeName + '\'' +
                        ", riskRateName='" + riskRateName + '\'' +
                        ", id=" + id +
                        ", pageNum=" + pageNum +
                        ", indexNum=" + indexNum +
                        '}';
            }

            public String getProductName() {
                return productName;
            }

            public void setProductName(String productName) {
                this.productName = productName;
            }

            public String getProductTypeName() {
                return productTypeName;
            }

            public void setProductTypeName(String productTypeName) {
                this.productTypeName = productTypeName;
            }

            public String getRiskRateName() {
                return riskRateName;
            }

            public void setRiskRateName(String riskRateName) {
                this.riskRateName = riskRateName;
            }

            public int getId() {
                return id;
            }

            public void setId(int id) {
                this.id = id;
            }

            public int getPageNum() {
                return pageNum;
            }

            public void setPageNum(int pageNum) {
                this.pageNum = pageNum;
            }

            public int getIndexNum() {
                return indexNum;
            }

            public void setIndexNum(int indexNum) {
                this.indexNum = indexNum;
            }
        }


    }
}

Service API部分

这里不展开说太多 Retrofit2部分的东西,这里只贴一下和本章有关的部分代码

    /**
     * 请求公募数据列表
     * @param map
     * @return
     */
    @GET(App_Url.admin_getPublicProductInfoPageList)
    Single<Response_public_info_bean> admin_getPublicProductInfoPageList(@QueryMap HashMap<String,String> map);

以上部分完成后 ,能够通过RxJava3 + Retrofit2 配合完成一次网络请求, 基本就算完成Demo一半功能了

Paging3

PagingSource以及 RxPagingSource

这里一开始我用错了RxPagingSource的导入版本, 用成了rxjava2的,踩了些坑
[Android+JetPack] (Java实现) Retrofit2+RxJava3+Paging3+RecyclerView 实现加载网络数据例子 记录_第4张图片
当时导入了paging.rxjava2这个版本,
主要原因还是在配置依赖的时候, 把paging-rxjava2:3.1.1版本也同步了
[Android+JetPack] (Java实现) Retrofit2+RxJava3+Paging3+RecyclerView 实现加载网络数据例子 记录_第5张图片
版本问题注意好

回到 RxPagingSource
按照网上的文章的示例, 先处理 loadSingle()函数的实现,这里有坑就是上面说的, RxJava3 + Paging3的情况下,
[Android+JetPack] (Java实现) Retrofit2+RxJava3+Paging3+RecyclerView 实现加载网络数据例子 记录_第6张图片
return网络请求的时候,会报错
[Android+JetPack] (Java实现) Retrofit2+RxJava3+Paging3+RecyclerView 实现加载网络数据例子 记录_第7张图片
我这里没有在详细探究是否由于RxJava2的原因导致不能用 ::这种双冒号的写法
这里仅仅贴一下RxJava2下的map 和 RxJava3下的map的源码区别
RxJava2版本:
[Android+JetPack] (Java实现) Retrofit2+RxJava3+Paging3+RecyclerView 实现加载网络数据例子 记录_第8张图片
RxJava3版本:
[Android+JetPack] (Java实现) Retrofit2+RxJava3+Paging3+RecyclerView 实现加载网络数据例子 记录_第9张图片
确实有一点区别, 这个先放一放, 等后期有空再看怎么处理…

先直接看怎么去写这个 this::toLoadResult
首先,既然通过Retrofit2,我们已经定义了网络请求的返回值
[Android+JetPack] (Java实现) Retrofit2+RxJava3+Paging3+RecyclerView 实现加载网络数据例子 记录_第10张图片
那么我们在RxPagingSourceloadSingle()中, 会去调用网络请求,得到一个 Single
[Android+JetPack] (Java实现) Retrofit2+RxJava3+Paging3+RecyclerView 实现加载网络数据例子 记录_第11张图片
我们再看官网例子的这部分代码
[Android+JetPack] (Java实现) Retrofit2+RxJava3+Paging3+RecyclerView 实现加载网络数据例子 记录_第12张图片
注意看返回值其实是LoadResult ,或者说,在本文章 我们要的返回值其实是 LoadResult
所以.对于map操作符,在Rxjava3的下, 我们是可以自己去提供一个Function,这里面T就是我们上面的Response_public_info_bean
R就是Response_public_info_bean.Datas.Cell>

所以代码就是

[Android+JetPack] (Java实现) Retrofit2+RxJava3+Paging3+RecyclerView 实现加载网络数据例子 记录_第13张图片
(这里要注意下prevkey, 和 nextKey的入参 , 要做好逻辑判断, 一开始我参考别人的代码, 在prevKey填的是null, 在nextKey填入的是nextPageKey+1,结果导致加载页码瞬间冲到了几百页, 其实总page数量才不过十几页)

为了更加清晰明了的展示页码和条数下标, 我又添加了一个map操作符, 是给cell这个Bean数据再添加一下当前所属的页码 和 当前的下标
Function的入参依然是 Response_public_info_bean 返回也还是 Response_public_info_bean, 相当于我们就对Response_public_info_bean数据做了个数据加工
[Android+JetPack] (Java实现) Retrofit2+RxJava3+Paging3+RecyclerView 实现加载网络数据例子 记录_第14张图片

所以,基于RxJava3的map操作符这边就可以这样返回
[Android+JetPack] (Java实现) Retrofit2+RxJava3+Paging3+RecyclerView 实现加载网络数据例子 记录_第15张图片

PagingDataAdapter 适配器

public class PublicInfoAdapter extends PagingDataAdapter<Response_public_info_bean.Datas.Cell, PublicInfoAdapter.Holder> {


    public PublicInfoAdapter(@NotNull DiffUtil.ItemCallback<Response_public_info_bean.Datas.Cell> diffCallback) {
        super(diffCallback);
    }


    @NonNull
    @NotNull
    @Override
    public Holder onCreateViewHolder(@NonNull @NotNull ViewGroup parent, int viewType) {
        
        PublicInfoCellBinding publicInfoCellBinding = PublicInfoCellBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false);
        return new Holder(publicInfoCellBinding);
    }



    @Override
    public void onBindViewHolder(@NonNull @NotNull Holder holder, int position) {
        Response_public_info_bean.Datas.Cell cell = getItem(position);
        Log.d("dfaddfsa", "onBindViewHolder: " + cell.getPageNum() +"."+ cell.getIndexNum() + "---" + cell.getProductName());

        holder.publicInfoCellBinding.textTitle.setText(cell.getProductName());
        holder.publicInfoCellBinding.underflag.setText("第"+cell.getPageNum() + "页.第" + cell.getIndexNum()+"条");
    }

    public class Holder extends RecyclerView.ViewHolder {

        /**
         * 给每个 Holder 实例 一个 viewBinding
         */
        private PublicInfoCellBinding publicInfoCellBinding;

        public Holder(@NonNull @NotNull View itemView) {
            super(itemView);
        }

        public Holder(@NonNull PublicInfoCellBinding publicInfoCellBinding){
            super(publicInfoCellBinding.getRoot());
            this.publicInfoCellBinding = publicInfoCellBinding;
        }
    }
}

这里没太多可以说的,网上基本讲明白了, 我仅仅分享我遇到的一个问题
页面列表在加载完成后, 出现了一屏数据的重复渲染, 而且随着页面的滚动,该组数据的最后一条一直在渲染不同内容(但随着日志打印,数据都是正常输出的)
最后通过UI观察, 感觉最后一条数据随滚动而渲染,有点想是for循环没拦住的那种意思
就猜想,是不是 PagingDataAdapter 里面没处理好,
后来果然发现, PublicInfoCellBinding publicInfoCellBinding;一不小心写成了全局的,而不是给每个Holder一个PublicInfoCellBinding publicInfoCellBinding;, 最后修复下即可

这里还是由于对PagingDataAdapter的不够熟悉, 刚写着玩意儿,才出现的低级错误

ViewModel

vm部分,网上也大同小异 ,写demo过程中未出现过多的坎儿

public class PublicInfoViewModel extends ViewModel {

    // paging3 page对象
    Pager<Integer, Response_public_info_bean.Datas.Cell> pager;
    // paging3 数据源对象
    PublicInfoSource publicInfoSource;
    // rxjava3 的 obserable 可观察对象
    Flowable<PagingData<Response_public_info_bean.Datas.Cell>> pagingDataFlowable;

    public PublicInfoViewModel(Context context) {
        CoroutineScope viewModelScope = ViewModelKt.getViewModelScope(this);

        publicInfoSource = new PublicInfoSource();

        // Maximum size must be at least pageSize + 2*prefetchDist, pageSize=20, prefetchDist=20, maxSize=20
        /**
         * pageSize 每页多少个条目
         * prefetchDistance 预加载下一页的距离,滑动到倒数第几个条目就加载下一页,无缝加载(可选)默认值是pageSize
         * enablePlaceholders 是否启用条目占位,当条目总数量确定的时候;列表一次性展示所有条目,
         *                    但是没有数据;在adapter的onBindViewHolder里面绑定数据时候,是空数据,判断是空数据展示对应的占位item;可选,默认开启
         * initialLoadSize 第一页加载条目数量 ,可选,默认值是 3*pageSize (有时候需要第一页多点数据可用)
         * maxSize : 定义列表最大数量;可选,默认值是:Int.MAX_VALUE
         * jumpThreshold : 暂时还不知道用法,从文档注释上看,是滚动大距离导致加载失效的阈值;可选,默认值是:Int.MIN_VALUE (表示禁用此功能)
         *
         */
        PagingConfig pagingConfig = new PagingConfig(20,1,false,20*3);

        pager = new Pager<Integer, Response_public_info_bean.Datas.Cell>(pagingConfig, () -> publicInfoSource);

        pagingDataFlowable = PagingRx.getFlowable(pager);

        PagingRx.cachedIn(pagingDataFlowable, viewModelScope);
    }


    public Flowable<PagingData<Response_public_info_bean.Datas.Cell>> getPagingDataFlowable() {
        return pagingDataFlowable;
    }
}

PublicInfoPage /Activity

这里要注意的是, setLayoutManager要设置 否则啥也不展示


public class PublicInfoPage extends AppCompatActivity {

    ActivityNewsPageBinding newsPageBinding;

    PublicInfoViewModel newsViewModel;
    PublicInfoAdapter newsAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        newsPageBinding = ActivityNewsPageBinding.inflate(getLayoutInflater());


        newsAdapter = new PublicInfoAdapter(new DiffUtil.ItemCallback<Response_public_info_bean.Datas.Cell>() {
            @Override
            public boolean areItemsTheSame(@NonNull @NotNull Response_public_info_bean.Datas.Cell oldItem, @NonNull @NotNull Response_public_info_bean.Datas.Cell newItem) {
                return oldItem.getId() == newItem.getId();
            }

            @Override
            public boolean areContentsTheSame(@NonNull @NotNull Response_public_info_bean.Datas.Cell oldItem, @NonNull @NotNull Response_public_info_bean.Datas.Cell newItem) {
                return oldItem.getProductName().equals(newItem.getProductName()) && oldItem.getProductTypeName().equals(newItem.getProductTypeName());
            }
        });
        newsPageBinding.recicleView.setAdapter(newsAdapter);
        newsPageBinding.recicleView.setLayoutManager(new LinearLayoutManager(this));

        setContentView(newsPageBinding.getRoot());
    }


    @Override
    protected void onResume() {
        super.onResume();

        newsViewModel = new PublicInfoViewModel(this);
        newsViewModel.getPagingDataFlowable().subscribe(new Consumer<PagingData<Response_public_info_bean.Datas.Cell>>() {
            @Override
            public void accept(PagingData<Response_public_info_bean.Datas.Cell> newsPagingData) throws Throwable {
                newsAdapter.submitData(getLifecycle(), newsPagingData);
            }
        });

    }
}

最后

以上就是这样了.
SmartRefreshLayout 也可以结合Paging3
这个有空也看一下,
安卓的玩法确实和iOS不一样, 也和RN不一样, 但互相又看得到对方的影子

你可能感兴趣的:(android,java,rxjava)