MVVM: ViewModel+LiveData+DataBinding+Retrofit+Room+Paging+RxJava 总结与实践(Java实现)

本文使用JetPack的ViewModel,LiveData, DataBinding, Room, Paging等技术实践。

内容如下:

一. 一两句话和一两个图总结 LiveData作用、ViewModel作用、DataBinding作用

二、Demo实践

      1. 准备、Demo效果和Demo结构、开发步骤

      2. Paging2 + LiveData + ViewModel + Room 分页 加载本地数据实践(Java实现)

      3. Paging2 + LiveData+ ViewModel  + Retrofit 分页 加载网络数据实践(Java实现)

      4. Paging3 + RxJava + ViewModel + Retrofit 分页加载网络数据实践(Java实现)

一. 一两句话和一两个图总结 LiveData作用、ViewModel作用、DataBinding作用:

    LiveData作用

    (1)实际上就是一个观察者模式的数据容器,当数据改变时,通知UI刷新;

    (2)数据LiveData<T>是观察者,组件activity, fragment是被观察者;

    (3)LiveData能感知activity, fragment组件的生命周期,当activity,fragment销毁时,会自动清除引用,不必担心内存泄漏。而其他观察者回调的库需要自己管理生命周期。

    (4)当activity或者fragment处于活动状态时如started,resumed,liveData才会通知。 

    (5)LiveData通常和ViewModel一起使用, LiveData<T>作为ViewModel的一个成员变量。

LiveData原理总结:https://blog.csdn.net/xiaobaaidaba123/article/details/123030339 

    ViewModel作用

    (1)将activity, fragment里关于数据操作的逻辑抽离出来,封装到ViewModel中,所以ViewMoel 持有一个成员变量LiveData<T>。

    (2)数据的操作包括什么呢? a. 从DB和缓存读取数据,显示到UI;  b. 通过网络到后台拉取数据,持久化到本地,更新DB和缓存,通知UI刷新。

    (3)因此ViewModel 应该持有一个 成员变量Repository(相当于一个管理类, 命名可以命名为其他如XXXManager),做(2)的事情。 而组件activity, fragment应该持有一个成员变量ViewModel , 如图所示

图片来源LiveData + ViewModel + Room (Google 官文)+Demo - 简书

    (4)ViewModel的生命周期如图, 可以看出 横竖屏切换,activity onDestroy后重新onCreate, ViewModel 还是原来的对象,没有被重新创建。

图片来源https://developer.android.com/topic/libraries/architecture/viewmodel

    (5)ViewModel 不能持有activity, fragment的引用,否则会导致内存泄漏, ViewMode中如果要使用context,我们通常定义XXXViewModel 继承 基类AndroidViewModel就好了。

    (6)ViewModel可以用于fragment之间通信。

    Databinding作用

     (1) 和UI双向绑定

     (2) 在build.gradle 的android下 声明

dataBinding {
    enabled = true
}

后, 在XML布局文件定义一个variable来引用我们的数据实体类。如:


 <variable name="poetry" type="com.mikel.projectdemo.jetpack.service.model.Poetry"/>

在XML具体的控件可以直接访问数据里的字段 如

android:text="@{poetry.title}"

(3)Android Studio 会根据XML的文件名生成一个Binding类,如

fragment_poetry_detail.xml ----- >  FragmentPoetryDetailBindingImpl

同时也生成相应的setXXX方法, 如在xml定义的variable变量:

variable name = "poetry"  ---->  setPoetry()------> 完成了Poetry和UI绑定

(4)  binding调用setXXX方法 实现数据同步到UI

    private void observeViewModelLocal(final PoetryViewModel viewModel) {
        viewModel.getmPoetryObservableLocal().observe(this, new Observer<Poetry>() {
            @Override
            public void onChanged(Poetry poetry) {
                Utils.printPoetryInfo(poetry);//打日志
                if (poetry != null) {
                    mFragmentPoetryDetailBinding.setIsLoading(false);// binding调用setXXX方法后,数据同步到UI
                    mFragmentPoetryDetailBinding.setPoetry(poetry);// binding调用setXXX方法后,数据同步到UI
                }
            }
        });
    }

------------------------------------------------二 Demo实践------------------------------------------------

2.1. 准备、Demo效果和Demo结构、开发步骤

    2.1.1 准备

       用到开源的api如下:  

       获取唐朝古诗词: 
       https://api.apiopen.top/getTangPoetry?page=1&count=20

       来源免费开放接口API(转载至有梦想的程序丶猿)_php_M的博客-CSDN博客

        但是最近该免费API 由于被大量使用暂时失效了,本Demo采用模拟网络服务返回的方式,同样达到想要的网络请求-响应效果。

     2.1.2 Demo效果和Demo结构:

    2.1.3 开发步骤

        1)在Build.gradle中添加依赖

plugins {
    id 'com.android.application'
}

android {
    compileSdkVersion 30

    defaultConfig {
        applicationId "com.mikel.projectdemo"
        minSdkVersion 21
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    /**
     * 使用databinding
     */
    buildFeatures {
        dataBinding = true
    }
}

dependencies {

    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'com.google.android.material:material:1.2.1'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'

    //添加okhttp依赖
    implementation 'com.squareup.okhttp3:okhttp:4.4.0'

    //添加retrofit依赖
    implementation 'com.squareup.retrofit2:retrofit:2.5.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
    implementation 'com.squareup.retrofit2:converter-scalars:2.5.0'
    implementation 'com.squareup.retrofit2:adapter-rxjava2:2.5.0'//支持RxJavaCallAdapterFactory

    //添加rxjava2依赖
    implementation 'io.reactivex.rxjava2:rxjava:2.2.9'
    implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'

    //paging2
//    implementation "androidx.paging:paging-runtime:2.1.0"

    //paging3
    implementation "androidx.paging:paging-runtime:3.0.1"
    implementation "androidx.paging:paging-rxjava2:3.0.1"

    //添加room依赖
    // add for room
    implementation "android.arch.persistence.room:runtime:1.1.1"
    // room 配合 RxJava
    implementation "android.arch.persistence.room:rxjava2:1.1.1"
    annotationProcessor 'android.arch.persistence.room:compiler:1.1.1'

    //ui recyclerView依赖添加
    implementation 'androidx.recyclerview:recyclerview:1.2.0'
}

        2)创建实体类Poetry  

Portry.java:

/**
 * 实体类—唐诗
 */
@Entity(tableName = "poetry_table") //数据库表名poetry_table
public class Poetry {
    @NonNull
    @ColumnInfo
    @PrimaryKey(autoGenerate = true) //主键
    public int poetry_id;
    @NonNull
    @ColumnInfo
    public String title;   //标题
    @NonNull
    @ColumnInfo //列
    public String content; //内容
    @NonNull
    @ColumnInfo //列
    public String authors;  // 作者

    public Poetry() {
    }

    @Override
    public String toString() {
        return "Poetry:" +
                "id=" + poetry_id +
                "title =" + title +
                ", content =" + content +
                ", author =" + authors;
    }

    public int getPoetry_id() {
        return poetry_id;
    }

    public void setPoetry_id(int poetry_id) {
        this.poetry_id = poetry_id;
    }

    @NonNull
    public String getTitle() {
        return title;
    }

    public void setTitle(@NonNull String title) {
        this.title = title;
    }

    @NonNull
    public String getContent() {
        return content;
    }

    public void setContent(@NonNull String content) {
        this.content = content;
    }

    @NonNull
    public String getAuthors() {
        return authors;
    }

    public void setAuthors(@NonNull String authors) {
        this.authors = authors;
    }
}

        3) 数据库层使用room框架

        a. 实体类 Poetry 使用@Entity标注,并且指定数据库的表名 poetry_table;

            poetry_id作为主键用@PrimaryKey标注;

            title, content和authors作为数据库表的列用@ColumnInfo标注。

        b. 定义DAO类,主要负责封装 增删改查 数据库的接口。

            注意:Poetry接口类需要用@Dao标注 ,每一个接口使用@Query + SQL查询语句标注

PoetryDao.java:   

@Dao  // DAO接口
public interface PoetryDao {
    /**
     * 查询所有
     * @return
     */
    @Query("SELECT * FROM poetry_table")
    List<Poetry> getAllPoetries();

    /**
     * 分页查询数据库
     * @param startId
     * @param size
     * @return
     */
    @Query("SELECT  * FROM poetry_table LIMIT :startId, :size")
    List<Poetry> getSpecificPoetries(int startId, int size);

    @Query("SELECT * FROM poetry_table WHERE title = :name")
    Poetry getPoetryByName(String name);

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    void insertAll(List<Poetry> poetries);

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    void insert(Poetry poetry);

    @Query("DELETE FROM poetry_table")
    void deleteAll();
}

        c. 定义继承RoomDatabase的AppDatabase,负责初始化数据库对象。

           注意:当后续要更改数据库字段的时候,需要实现方法migrate()

AppDatabase.java: 

@Database(entities = {Poetry.class, RemoteKey.class}, version = 1, exportSchema = false) // 声明版本号1
public abstract class AppDatabase extends RoomDatabase {
    private static AppDatabase INSTANCE;
    public abstract PoetryDao poetryDao();
    public abstract RemoteKeyDao remoteKeyDao();

    public static AppDatabase getInstance(Context context) {
        if(INSTANCE == null) {//单例设计模式 双重校验
            synchronized (AppDatabase.class) {
                if(INSTANCE == null) {
                    INSTANCE = Room.databaseBuilder(context.getApplicationContext(),AppDatabase.class,
                            "mikel_room.db").
                            addMigrations(AppDatabase.MIGRATION_1_2).// 修改数据库字段时更新数据库
                            allowMainThreadQueries().//允许在主线程读取数据库
                            build();
                }
            }
        }
        return INSTANCE;
    }

    /**
     * 数据库变动添加Migration
     */
    public static final Migration MIGRATION_1_2 = new Migration(1, 2) {
        @Override
        public void migrate(SupportSQLiteDatabase database) {
            // todo 如果有修改数据库的字段 可以使用database.execSQL() 同时 修改数据库的version
        }
    };
}

     总结a , b, c 步骤描述了room框架的使用。

        4)绑定使用DataBinding,异步通信使用  ViewModel + LiveData

首先需要在build.gradle的android下 添加

dataBinding {
    enabled = true
}

以详情页的 布局文件为例 fragment_poetry_detail.xml ,其对应一个ViewModel类 PoetryViewModel

<?xml version="1.0" encoding="utf-8"?>

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <!--databinding 使用isLoading 代表一个布尔变量-->
        <variable name="isLoading" type="boolean" />
        <!--databinding 使用poetry可以直接访问Poetry里的字段-->
        <variable name="poetry" type="com.mikel.projectdemo.jetpack.service.model.Poetry"/>
    </data>

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <TextView
                android:id="@+id/loadingTips"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:gravity="center_vertical|center_horizontal"
                android:text="loading......"
                android:textAlignment="center"
                app:visibleGone="@{isLoading}"/>

            <LinearLayout
                android:layout_marginStart="8dp"
                android:layout_marginEnd="8dp"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:gravity="center_vertical|center_horizontal"
                android:padding="5dp"
                android:paddingTop="16dp"
                android:orientation="vertical"
                app:visibleGone="@{!isLoading}">

                <ImageView
                    android:id="@+id/imageView"
                    android:layout_width="150dp"
                    android:layout_height="125dp"
                    android:src="@drawable/image" />

                <TextView
                    android:id="@+id/name"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:textStyle="bold"
                    android:textSize="25sp"
                    android:text="@{poetry.title}"
                    android:textAlignment="center"
                    android:paddingBottom="5dp"
                    android:gravity="center_horizontal" />

                <TextView
                    android:id="@+id/project_desc"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:textSize="20sp"
                    android:text="@{poetry.content}"/>


                <TextView
                    android:id="@+id/languages"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:textSize="16sp"
                    android:text="@{poetry.authors}"/>

            </LinearLayout>

        </FrameLayout>


    </ScrollView>

</layout>

如上图,xml布局代码所示,data标签下


<variable name="poetry" type="com.mikel.projectdemo.jetpack.service.model.Poetry"/>

意思是:使用poetry可以直接访问实体类Poetry里的字段, 如android:text="@{poetry.title}"

涉及到数据的操作逻辑应该都封装到ViewModel中 , 随着项目代码逻辑增多,数据操作的逻辑还可以封装到数据仓库类 可以命名为XXXManager。

PoetryViewModel.java类如下:

     /**
     * 使用同一个obserable监听  只会响应最后的一个
     * 这里区分本地的和network的
     */
    // MutableLiveData继承liveData, 提供了setValue和postValue方法。liveData 是抽象类
    private MutableLiveData<Poetry> mPoetryObservableLoacl= new MutableLiveData<>();
    private MutableLiveData<Poetry> mPoetryObservableNetwork = new MutableLiveData<>();
    private final String poetryID;

    public PoetryViewModel(Application application, String poetryID) {
        super(application);
        this.poetryID = poetryID;
    }

    /**
     * 读DB或者缓存
     * 根据具体的名字获取Poetry
     */
    public Poetry loadDataInfo(String name) {
        Utils.printMsg(" poetry detail load info");
        Poetry poetry = AppDatabase.getInstance(this.getApplication()).poetryDao().getPoetryByName(name);
        mPoetryObservableLoacl.setValue(poetry);// setValue 需要在主进程调用
        return poetry;
    }

    /**
     *  发送网络请求
     *  真实的请求逻辑封装到了RetrofitManager
     * 根据名称搜索某个poetry
     * @param name
     */
    public void requestSearchPoetry(String name) {
        mPoetryObservableNetwork = RetrofitManager.getInstance(this.getApplication()).searchPoetry(name);
    }

    /**
     * 返回LiveData 对象
     * @return
     */
    public LiveData<Poetry> getmPoetryObservableNetwork() {
        return mPoetryObservableNetwork;
    }

    public LiveData<Poetry> getmPoetryObservableLocal() {
        return mPoetryObservableLoacl;
    }

    public static class Factory extends ViewModelProvider.NewInstanceFactory {

        @NonNull
        private final Application application;

        private final String poetryID;

        public Factory(@NonNull Application application, String poetryID) {
            this.application = application;
            this.poetryID = poetryID;
        }

        @Override
        public <T extends ViewModel> T create(Class<T> modelClass) {
            //noinspection unchecked
            return (T) new PoetryViewModel(application, poetryID);
        }
    }
}

这里遇到了一个问题, 如果同一个MutableLiveData,读本地数据后setValue 和 网络回包setValue, Observer.onChanged只响应最后setValue的那一次。 所以这里区分

private MutableLiveData<Poetry> mPoetryObservableLocal= new MutableLiveData<>();
private MutableLiveData<Poetry> mPoetryObservableNetwork = new MutableLiveData<>();

         5)网络层使用Retrofit框架

        a. 请求接口的定义

public interface PagingNetWorkApiService {
    public static String HTTPS_API_OPEN_URL = "https://api.apiopen.top/";

    //获取唐诗列表Rxjava
    @GET("getTangPoetry")
    Observable<ResultList<Poetry>> fetchPoetryListForRxJava(@Query("page") String page, @Query("count") String count);


    @GET("getTangPoetry")
    Single<ResultList<Poetry>> fetchPoetryListForPaging3(@Query("page") String page, @Query("count") String count);

    //根据名称搜索唐诗
    @GET("searchPoetry")
    Observable<ResultList<Poetry>> searchPoetry(@Query("name") String name);


}

? 后面带有参数  使用@Query, 如果参数是在URL路径中, 使用 @Path

Retrofit可以和Rxjava结合 返回Observable对象,也能和LiveData结合返回LiveData对象

后文分别介绍

Paging2 + LiveData + ViewModel + Room   分页加载本地数据(Java实现)

Paging2 + LiveData + ViewModel + Retrofit  分页加载网络数据(Java实现)

Paging3 + RxJava+ ViewModel  + Retrofit   分页加载网络数据(Java实现)

2.2  Paging2 + LiveData + ViewModel + Room 分页 加载本地数据实践(Java实现)

网络上有不少帖子使用Paging框架分页加载本地数据,但是并不是真正的分页,而是一次性加载数据库的数据后,仅仅是在UI上进行分页。

笔者将从  逻辑+UI  分页加载本地数据库。 

DAO关键查询接口:(传递参数startId:起始item项id,和size :分页大小)

   /**
     * 分页查询数据库
     * @param startId
     * @param size
     * @return
     */
    @Query("SELECT  * FROM poetry_table LIMIT :startId, :size")
    List<Poetry> getSpecificPoetries(int startId, int size);
Paging2本地分页数据源LocalPageKeyedDataSource 实现
public class LocalPageKeyedDataSource extends PageKeyedDataSource<Integer, Poetry> {
    public static final int PAGE_SIZE = 3;
    private PoetryDao mPoetryDao;
    public LocalPageKeyedDataSource(PoetryDao poetryDao) {
        mPoetryDao = poetryDao;
    }

    @Override
    public void loadInitial(@NonNull @NotNull LoadInitialParams<Integer> params, @NonNull @NotNull LoadInitialCallback<Integer, Poetry> callback) {
        /**
         * 初始化只从数据库查首页 pageKey = 0, 下一页pageKey = 1
         */
        List<Poetry> firstPageList = mPoetryDao.getSpecificPoetries(0, PAGE_SIZE);
        Log.w(Constants.TAG, " load initial ");
        Utils.printPoetryInfo(firstPageList);
        callback.onResult(firstPageList, null, 1);
    }

    @Override
    public void loadBefore(@NonNull @NotNull LoadParams<Integer> params, @NonNull @NotNull LoadCallback<Integer, Poetry> callback) {
        Log.w(Constants.TAG, " load before param key =  " + params.key);
    }

    @Override
    public void loadAfter(@NonNull @NotNull LoadParams<Integer> params, @NonNull @NotNull LoadCallback<Integer, Poetry> callback) {
        int startID = params.key * params.requestedLoadSize;
        Log.w(Constants.TAG, " load after  start id = " + startID + " size= " + params.requestedLoadSize);
        /**
         * 每次只从数据库里 查一页数据
         */
        List<Poetry> dataList = mPoetryDao.getSpecificPoetries(startID, params.requestedLoadSize);
        Utils.printPoetryInfo(dataList);
        if (dataList != null) {
            callback.onResult(dataList, params.key + 1);
        }
    }
}

viewmodel实现:

public class PoetryListViewModelForPaging2Local extends AndroidViewModel {
    PoetryDao mPortryDao;
    private LiveData<PagedList<Poetry>> localLiveDataPageListPoetry;
    private LocalPageKeyedDataSourceFactory mLocalPageKeyedDataSourceFactory;
    public PoetryListViewModelForPaging2Local(Application application) {
        super(application);

        mPortryDao =  AppDatabase.getInstance(this.getApplication()).poetryDao();
        //本地数据源
        mLocalPageKeyedDataSourceFactory = new LocalPageKeyedDataSourceFactory(mPortryDao);
        localLiveDataPageListPoetry =  new LivePagedListBuilder<>(mLocalPageKeyedDataSourceFactory,
                LocalPageKeyedDataSource.PAGE_SIZE).build();
    }

    public LiveData<PagedList<Poetry>> getLocalLiveDataPageListPoetry() {
        return localLiveDataPageListPoetry;
    }
}

2.3. Paging2 + LiveData+ ViewModel  + Retrofit 分页 加载网络数据实践(Java实现)

Paging2网络分页数据源NetPagedKeyedDataSource.java实现
public class NetPagedKeyedDataSource extends PageKeyedDataSource<Integer, Poetry> {
    public static final int PAGE_SIZE = 3;
    private int CUR_PAGE = 0;
    private Context mContext;
    public NetPagedKeyedDataSource(Context context) {
        this.mContext = context;
    }
    @Override
    public void loadInitial(@NonNull @NotNull LoadInitialParams<Integer> params, @NonNull @NotNull LoadInitialCallback<Integer, Poetry> callback) {
        Log.w(Constants.TAG, "network  load intial ");
        CUR_PAGE = 0;

        /**
         *  todo
         *  由于网络接口失效,这里构建测试数据 模拟服务端的返回,
         *  正常情况下直接使用下面被注释掉的代码 RetrofitManager.getInstance(mContext).fetchPoetryList
         */
//        RetrofitManager.getInstance(mContext).fetchPoetryList(String.valueOf(0), String.valueOf(PAGE_SIZE), new RetrofitManager.ReqCallBack() {
//            @Override
//            public void updateData(List<Poetry> dataList) {
//                Utils.printPoetryInfo(dataList);
//                callback.onResult(dataList, null, 1);
//            }
//        });
        buildNetTestData(String.valueOf(0), new RetrofitManager.ReqCallBack() {
            @Override
            public void updateData(List<Poetry> dataList) {
                Utils.printPoetryInfo(dataList);
                callback.onResult(dataList, null, 1);
            }
        });

    }

    @Override
    public void loadBefore(@NonNull @NotNull LoadParams<Integer> params, @NonNull @NotNull LoadCallback<Integer, Poetry> callback) {

    }

    @Override
    public void loadAfter(@NonNull @NotNull LoadParams<Integer> params, @NonNull @NotNull LoadCallback<Integer, Poetry> callback) {
        CUR_PAGE = params.key;
        int startID = params.key * params.requestedLoadSize;
        Log.w(Constants.TAG, "network  load after  start id = " + startID + " size= " + params.requestedLoadSize);
        /**
         *  todo
         *  由于网络接口失效,这里构建测试数据 模拟服务端的返回,
         *  正常情况下直接使用下面被注释掉的代码 RetrofitManager.getInstance(mContext).fetchPoetryList
         */
//        RetrofitManager.getInstance(mContext).fetchPoetryList(String.valueOf(params.key),
//                String.valueOf(params.requestedLoadSize), new RetrofitManager.ReqCallBack() {
//                    @Override
//                    public void updateData( List<Poetry> dataList) {
//                        Utils.printPoetryInfo(dataList);
//                        if (dataList != null) {
//                            callback.onResult(dataList, params.key + 1);
//                        }
//                    }
//                });

        buildNetTestData(String.valueOf(params.key), new RetrofitManager.ReqCallBack() {
            @Override
            public void updateData(List<Poetry> dataList) {
                Utils.printPoetryInfo(dataList);
                if (dataList != null) {
                    callback.onResult(dataList, params.key + 1);
                }
            }
        });
    }

    private void buildNetTestData(String page, RetrofitManager.ReqCallBack callback) {
        List<Poetry> poetries = new ArrayList<>();
        for(int i=0; i<3; i++){
            long time = System.currentTimeMillis() + i;
            Poetry poetry = new Poetry();
            poetry.poetry_id = (int)time;
            poetry.title = "NetworkResponse_页" + page + "_no." + i
                    +"_" + RepositoryManager.CACHE_TITLE[i] + "_" + time;
            poetry.content= RepositoryManager.CACHE_CONTENT[i];
            poetry.authors= RepositoryManager.CACHE_AUTHOR[i];
            poetries.add(poetry);
        }
        //回调数据
        callback.updateData(poetries);
    }
}
由于网络接口失效,这里构建测试数据 模拟服务端的请求-响应。

viewmodel实现:

public class PoetryListViewModelForPaging2Network extends AndroidViewModel {
    private LiveData<PagedList<Poetry>> netLiveDataPageListPoetry;
    private NetPagedKeyedDataSourceFactory mNetPagedKeyedDataSourceFactory;
    public PoetryListViewModelForPaging2Network(Application application) {
        super(application);
        //网络数据源
        mNetPagedKeyedDataSourceFactory = new NetPagedKeyedDataSourceFactory(getApplication());
        netLiveDataPageListPoetry =  new LivePagedListBuilder<>(mNetPagedKeyedDataSourceFactory,
                LocalPageKeyedDataSource.PAGE_SIZE).build();
    }
    public LiveData<PagedList<Poetry>> getNetLiveDataPageListPoetry() {
        return netLiveDataPageListPoetry;
    }
}

2.4. Paging3 + RxJava + ViewModel + Retrofit 分页加载网络数据实践(Java实现)

Paging3网络分页数据源PoetryListPagingSource.java实现
public class PoetryListPagingSource extends RxPagingSource<Integer, Poetry> {
    @NonNull
    private PagingNetWorkApiService mPagingNetWorkApiService;
    private int nextPageKey = 0;
    public PoetryListPagingSource(@NonNull PagingNetWorkApiService pagingNetWorkApiService) {
        mPagingNetWorkApiService = pagingNetWorkApiService;
    }

    @NotNull
    @Override
    public Single<LoadResult<Integer, Poetry>> loadSingle(
            @NotNull LoadParams<Integer> params) {
        Integer nextPageNumber = params.getKey();
        if (nextPageNumber == null) {
            nextPageNumber = 0;
        }

        nextPageKey = nextPageNumber;

        Log.d(Constants.TAG, "Paging3框架,PagingSource loadSingle call,  page = " + nextPageKey);

        /**
         *  todo
         *  由于网络接口失效,这里构建测试数据 模拟服务端的返回,
         *  正常情况下直接使用下面被注释掉的代码mPagingNetWorkApiService.fetchPoetryListForPaging3
         */
//        return mPagingNetWorkApiService.fetchPoetryListForPaging3(String.valueOf(nextPageKey),
//                String.valueOf(Constants.PAGING_PAGE_SIZE))
//                .subscribeOn(Schedulers.io())
//                .map(this::toLoadResult)
//                .onErrorReturn(LoadResult.Error::new);
        ResultList<Poetry> testResultList = buildTestResultList(String.valueOf(nextPageKey));
        return Single.just(toLoadResult(testResultList));
    }

    private LoadResult<Integer, Poetry> toLoadResult(
            @NonNull ResultList<Poetry> resultList) {
        return new LoadResult.Page<>(
                resultList.getData(),
                null, // Only paging forward.
                nextPageKey + 1,
                LoadResult.Page.COUNT_UNDEFINED,
                LoadResult.Page.COUNT_UNDEFINED);
    }

    @Nullable
    @Override
    public Integer getRefreshKey(@NotNull PagingState<Integer, Poetry> state) {
        Integer anchorPosition = state.getAnchorPosition();
        if (anchorPosition == null) {
            return null;
        }

        LoadResult.Page<Integer, Poetry> anchorPage = state.closestPageToPosition(anchorPosition);
        if (anchorPage == null) {
            return null;
        }

        Integer prevKey = anchorPage.getPrevKey();
        if (prevKey != null) {
            return prevKey + 1;
        }

        Integer nextKey = anchorPage.getNextKey();
        if (nextKey != null) {
            return nextKey - 1;
        }

        return null;
    }

    /**
     * 模拟服务端返回,构造分页测试数据
     * @param page
     * @return
     */
    private ResultList<Poetry>  buildTestResultList(String page) {
        ResultList<Poetry> resultList = new ResultList<>();
        List<Poetry> poetries = new ArrayList<>();
        for(int i=0; i<3; i++){
            long time = System.currentTimeMillis() + i;
            Poetry poetry = new Poetry();
            poetry.poetry_id = (int)time;
            poetry.title = "NetworkResponse_第" + page + "页_第" + i
                    +"条数据_" + RepositoryManager.CACHE_TITLE[i] + "_" + time;
            poetry.content= RepositoryManager.CACHE_CONTENT[i];
            poetry.authors= RepositoryManager.CACHE_AUTHOR[i];
            poetries.add(poetry);
        }
        resultList.setCode(200);
        resultList.setMessage("Test Data Success");
        resultList.setResult(poetries);
        return resultList;
    }
}

由于网络接口失效,这里构建测试数据 模拟服务端的请求-响应。

viewmodel实现:

public class PoetryListViewModelForPaging3 extends ViewModel {
    Pager<Integer, Poetry> mPager;
    PoetryListPagingSource mPoetryListPagingSource;

    //rxjava observable
    Observable<PagingData<Poetry>> mPoetryObservable;
    public PoetryListViewModelForPaging3(Context context) {
        CoroutineScope viewModelScope = ViewModelKt.getViewModelScope(this);
        /**
         * 数据源
         */
        mPoetryListPagingSource = new PoetryListPagingSource(RetrofitManager.getInstance(context).getPagingNetWorkApiService());
        /**
         * Pager :分页大管家, 使用网络数据源构造
         */
        mPager = new Pager<Integer, Poetry>(new PagingConfig(Constants.PAGING_PAGE_SIZE), () -> mPoetryListPagingSource);

        /**
         *  PagingRx.getObservable
         */
        mPoetryObservable = PagingRx.getObservable(mPager);
        PagingRx.cachedIn(mPoetryObservable, viewModelScope);
    }

    public Observable<PagingData<Poetry>> getPoetryObservable() {
        return mPoetryObservable;
    }
}

Demo地址:

GitHub - mikelhm/MikelProjectDemo: Personal Android Demo

该工程主要是个人学习和实践 android 技术 的 Demo 集成

  1. MVVM: ViewModel+LiveData+DataBinding+Retrofit+Room+Paging+RxJava 总结与实践(Java实现)
    MVVM: ViewModel+LiveData+DataBinding+Retrofit+Room+Paging+RxJava 总结与实践(Java实现)_xiaobaaidaba123的专栏-CSDN博客
  2. android 嵌套ViewPager + Fragment实现仿头条UI框架Demo
    android 嵌套ViewPager + Fragment实现仿头条UI框架Demo_xiaobaaidaba123的专栏-CSDN博客
  3. Android 使用ViewPager2+ExoPlayer+VideoCache 实现仿抖音视频翻页播放
    https://blog.csdn.net/xiaobaaidaba123/article/details/120630087

-------------------------------------------------------------------------------------------------------------------------------

参考:

Android架构组件(二)——LiveData_sd_zhuzhipeng的专栏-CSDN博客 Android架构组件(二)——LiveData

LiveData + ViewModel + Room (Google 官文)+Demo - 简书 LiveData + ViewModel + Room (Google 官文)+Demo

Mvvm模式: Databinding 与 ViewModel+LiveData+Repository - 简书Mvvm模式: Databinding 与 ViewModel+LiveData+Repository

Android DataBinding (一) 基本用法 - 简书 databinding用法

  • 12
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 25
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 25
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值