android中常用的下拉刷新加载更多_Android Paging组件在MVVM架构中的使用指南(二)...

一 前言

在上一篇Android Paging组件在MVVM架构中的使用指南(一)中,我初步介绍了Android Jetpack Paging组件在MVVM架构中的基础使用方法,其中数据源使用了Github api也就是网络数据源,因此我们自然会想到:网络连接错误怎么办?事实上,如果按照上一篇中实现的话,一旦在数据分页时遇到网络连接错误,即使网络重新稳定,也不会重新触发分页了,这显然是不能接受的,因此我将在本篇着手处理网络连接错误

官方文档中关于处理网络连接错误中提到:

使用网络对使用分页库显示的数据进行抓取或分页时,切记不要始终将网络视为“可用”或“不可用”,因为许多连接会断断续续或不稳定:

  • 特定服务器可能无法响应网络请求。
  • 设备可能连接到速度较慢或信号较弱的网络。

您的应用应检查每个请求是否失败,并在网络不可用的情况下尽可能正常恢复。例如,如果数据刷新步骤不起作用,您可以提供“重试”按钮供用户选择。如果在数据分页步骤中发生错误,则最好自动重新尝试分页请求。

我认为这段话比较重要的有三点:

  1. 不能通过判断当前网络是否连通判断网络请求是否成功;
  2. 下拉刷新不成功时,页面要显示重试按钮;
  3. 数据下滑分页加载不成功时,需要在下一次滚动时尝试分页请求。

这是官方文档的推荐做法,也是我在这篇文章中最终实现的方式。

二 思路

明确一下页面数据加载逻辑。

  1. 下拉刷新:将调用loadInitial方法和loadAfter方法,在loadInitial成功时即显示下拉刷新成功,失败时即显示下拉刷新失败,并出现重试按钮,点击重试会重新请求数据;

7c587c21480788e9516bd7faf87f002f.gif

    

8d85e896e8f90e5c6ac10466980775ad.gif

  1. 上拉加载:存在两种情况,上拉加载会出现:

  • 用户向下滑动过快,数据还未填充,上拉加载会出现,但此时实际上已调用了loadAfter方法,只是数据还未加载成功,加载成功后即显示上拉加载成功,注意此时为了避免重复加载数据,需要去除已添加的数据加载事件,失败后即显示上拉加载失败,并添加数据加载事件,以备下次加载数据;

a7e1a78431a6367b891d1eb8c5f2770c.gif

80e7d1bb8ace468dec0200444c878923.gif

  • 用户向下滑动时,loadAfter方法出现错误,此时需要为上拉加载添加加载事件,用户在下一次滚动时,将出现上拉加载,并调用添加的事件加载数据,后续操作同上。

这个的展示效果不明显,读者可自行测试。

全部数据加载完成后,上拉加载需要显示无更多数据

cf02f78d012c370bd4b5e90e5a197abe.gif

这三点就是我们需要考虑的页面数据加载逻辑。

根据页面逻辑可以设计出回调接口为:

public interface NetworkState {
    // 加载成功
    void onSuccess();

    // 加载中
    void onLoading();

    // 初始加载|下拉刷新
    void onLoadInitialError(Runnable runnable, String errorMessage);

    // 分页加载|上拉加载
    void onLoadAfterError(Runnable runnable, String errorMessage);

    // 数据加载全部完成
    void onFinish();
}

下面讲解该接口的具体使用方法。

三 安装

implementation "com.google.android.material:material:1.2.1" // snack bar
implementation 'com.scwang.smart:refresh-layout-kernel:2.0.1'      //核心必须依赖
implementation 'com.scwang.smart:refresh-header-classics:2.0.1'    //经典刷新头

相比于第一篇,添加了谷歌material库和下拉刷新SmartRefreshLayout[1]库。

四 实现

4.1 完善GithubDataSource

主要是对loadInitialloadAfter方法添加错误处理:

@Override
public void loadInitial(@NonNull LoadInitialParams params, @NonNull LoadInitialCallback callback) {
    networkState.onLoading(); // 数据加载中
    Call call = githubService.query(query, sort, FIRST_PAGE, PAGE_SIZE);try {
        Response resResponse = call.execute();
        GithubRes res = resResponse.body();if (res != null) {// 数据加载成功
            networkState.onSuccess();
            callback.onResult(res.items, null, FIRST_PAGE + 1);
        } else
            networkState.onLoadInitialError(() -> loadInitial(params, callback), "访问错误!"); // 数据为空
    } catch (IOException e) {
        e.printStackTrace();
        networkState.onLoadInitialError(() -> loadInitial(params, callback), e.getMessage()); // 数据加载失败
    }
}@Overridepublic void loadAfter(@NonNull LoadParams params, @NonNull LoadCallback callback) {
    Call call = githubService.query(query, sort, params.key, PAGE_SIZE);try {
        GithubRes res = call.execute().body();if (res != null) {
            networkState.onSuccess();if (!res.complete)
                callback.onResult(res.items, params.key + 1);else {
                callback.onResult(res.items, null);
                networkState.onFinish();
            }
        } else
            networkState.onLoadAfterError(() -> loadAfter(params, callback), "访问错误!");
    } catch (IOException e) {
        e.printStackTrace();// 回调
        networkState.onLoadAfterError(() -> loadAfter(params, callback), e.getMessage());
    }
}

仔细的读者会发现我仅在loadInitial中添加了networkState.onLoading()方法,这是因为上拉加载会有单独的loading动画。

4.3 在Activity中实现接口

main_activity.xml

<?xml  version="1.0" encoding="utf-8"?>
<com.scwang.smart.refresh.layout.SmartRefreshLayout xmlns:tools="http://schemas.android.com/tools"xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/refreshLayout"android:layout_width="match_parent"android:layout_height="match_parent">
    <LinearLayoutandroid:id="@+id/container"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"android:orientation="vertical">
        <LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal">
            <EditTextandroid:id="@+id/search_repo"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:hint="请输入搜索内容"android:imeOptions="actionSearch"android:inputType="textNoSuggestions"android:selectAllOnFocus="true"android:text="Android"/>
            <Buttonandroid:id="@+id/search"android:text="搜索"android:backgroundTint="@color/colorAccent"android:layout_width="wrap_content"android:layout_height="wrap_content"/>
        LinearLayout>
        <androidx.recyclerview.widget.RecyclerViewandroid:id="@+id/recycler_view"android:layout_width="match_parent"android:layout_height="wrap_content"/>
        <ProgressBarandroid:id="@+id/progress_circular"android:layout_width="match_parent"android:layout_height="wrap_content"android:visibility="gone"/>
    LinearLayout>
com.scwang.smart.refresh.layout.SmartRefreshLayout>

添加了SmartRefreshLayoutProgressBar

其中SmartRefreshLayout用于下拉刷新和上拉加载,ProgressBar会在初始加载页面中出现。

onSuccess方法实现:

public void onSuccess() {
    runOnUiThread(() -> {
        progressBar.setVisibility(View.GONE);
        if (refresh.getState() == RefreshState.Refreshing)
            refresh.finishRefresh(true);
        if (refresh.getState() == RefreshState.Loading){
            refresh.finishLoadMore(true);
          refresh.setOnLoadMoreListener(null);   
        }
    });
}

此处必须在Ui线程对页面元素的可见性进行修改,因此使用了runOnUiThread方法进行了包装,这里的finishRefreshfinishLoadMore表示下拉刷新和上拉加载已经成功,结束展示。

onLoading方法实现:

public void onLoading() {
    runOnUiThread(() -> {
        progressBar.setVisibility(View.VISIBLE);
    });
}

上文中也已经提到,该方法将仅在初次加载时被触发。

onLoadInitialError方法实现:

public void onLoadInitialError(Runnable runnable, String errorMessage) {
    runOnUiThread(() -> {
        progressBar.setVisibility(View.GONE);
        if (refresh.getState() == RefreshState.Refreshing)
            refresh.finishRefresh(false);
    });
    Snackbar.make(findViewById(android.R.id.content), errorMessage, Snackbar.LENGTH_INDEFINITE)
            .setAction("RETRY", view -> executorService.submit(runnable)).show();
}

初次加载成功,progressBar将调用onSuccess方法隐藏,否则调用onLoadInitialError方法隐藏,由于已经设置在不满一页时不可以上拉加载,因此在这里无需处理上拉加载的状态。

onLoadAfterError方法实现:

public void onLoadAfterError(Runnable runnable, String errorMessage) {
    runOnUiThread(() -> {
        if (refresh.getState() == RefreshState.Loading)
            refresh.finishLoadMore(false);
    });
    refresh.setOnLoadMoreListener(refreshLayout -> executorService.submit(runnable));
}

对应于页面数据加载逻辑:

  1. 当用户向下滑动过快,数据还未填充,上拉加载将出现,但此时DataSource实际上已调用了loadAfter方法,成功后将调用onSuccess方法隐藏,并通过refresh.setOnLoadMoreListener(null);代码删除绑定事件,避免重复加载数据,失败后将调用onLoadAfterError方法隐藏,并通过refresh.setOnLoadMoreListener(refreshLayout -> executorService.submit(runnable));代码绑定事件,用于下次请求数据;
  2. 用户向下滑动时,loadAfter方法出现错误,将调用onLoadAfterError方法,为上拉加载绑定事件,在用户下次上拉时会调用绑定的事件,成功后将调用onSuccess方法隐藏,并删除绑定事件,失败后将调用onLoadAfterError方法隐藏,并绑定事件,用户下次上拉时将重复该流程。

onFinish方法实现:

public void onFinish() {
    runOnUiThread(refresh::finishLoadMoreWithNoMoreData);
}

SmartRefreshLayout确实封装的相当好了,只需要这一个方法即可完成任务。

刷新方法实现:

VieModel:
private MutableLiveData mGithubDataSource;public void setItemPagedList(String query, String sort) {
    GithubDataSourceFactory factory = new GithubDataSourceFactory(query, sort, networkState);
    ---
    mGithubDataSource = factory.getmGithubDataSource();
    ---
}public void invalidate() {if (mGithubDataSource != null && mGithubDataSource.getValue() != null)
        mGithubDataSource.getValue().invalidate();
}
Activity:
refresh.setOnRefreshListener(refreshLayout -> {
    mainViewModel.invalidate();
});

用户下拉刷新时,若成功,将调用onSuccess方法隐藏,否则调用onLoadInitialError方法隐藏,并出现Snackbar,显示重试按钮,点击重试按钮即可重新加载数据,当然也可以下拉刷新重新加载数据。

五 结语

实际上,在开始写Paging博客之前,我在项目开发时就已经被这个问题阻碍了很久,一直没有想到一种比较好的解决方式,网上关于Paging的博客也大多未介绍处理网络错误的部分,或者处理的不那么优雅,这次我花费了一天的时间,认真思考,也耐心的阅读了很多使用Kotlin语言处理网络错误的文章,他们给了我很大的启发,感谢他们!最终,自认为找到了一种相对优雅的实现方式,当然,还是存在很多可以改进的地方,如果各位有更好的方式,或者在使用中发现了问题,还麻烦批评指正。

六 源码

源码已经上传至github[2],欢迎star。

七 参考

  1. Using Android Paging library with Retrofit[3]
  2. Paging Library with Android MVVM[4]
  3. Android官方文档[5]

参考资料

[1]

SmartRefreshLayout: https://github.com/scwang90/SmartRefreshLayout

[2]

github: https://github.com/Civitasv/Android-Paging

[3]

Using Android Paging library with Retrofit: https://medium.com/@SaurabhSandav/using-android-paging-library-with-retrofit-fa032cac15f8

[4]

Paging Library with Android MVVM: https://www.linkedin.com/pulse/paging-library-android-mvvm-paul-hundal

[5]

Android官方文档: https://developer.android.com/topic/libraries/architecture/paging

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值