JetPack Paging3

1. Paging是什么

Paging 库可加载和显示来自本地存储或网络中更大的数据集中的数据页面。是一个分页库。

2. Paging的优势

  • 分页数据的内存中缓存。该功能可确保您的应用在处理分页数据时高效利用系统资源。
  • 内置的请求重复信息删除功能,可确保您的应用高效利用网络带宽和系统资源。
  • 可配置的RecyclerView适配器,会在用户滚动到已加载数据的末尾时自动请求数据。
  • 对 Kotlin 协程和 Flow 以及LiveData和RxJava的一流支持。
  • 内置对错误处理功能的支持,包括刷新和重试功能。

3. Paging中的三个重要类

3.1 PagingSource数据源

数据来源,可以是本地数据库源,也可以是从网络数据源。实现load()方法,PagingSource<Key, Value> 有两种类型参数:KeyValue。键定义了用于加载数据的标识符,值是数据本身的类型。

LoadParams对象包含有关要执行的加载操作的信息,其中包括要加载的键和要加载的项数。

LoadResult对象包含加载操作的结果。LoadResult 是一个密封的类,根据 load() 调用是否成功,采用如下两种形式之一:

  • 如果加载成功,则返回 LoadResult.Page 对象。
  • 如果加载失败,则返回 LoadResult.Error 对象。

主要的数据源:

  • 本地数据库源,Room可直接支持,比如下面的代码:

    @Dao
    interface UserDao {
    
        @Insert(onConflict = OnConflictStrategy.REPLACE)
        @Transaction
        fun insertUsers(users: List<User>)
    
    	//支持返回一个数据源
        @Query("SELECT * FROM user ORDER BY id COLLATE NOCASE ASC")
        fun allUserById(): PagingSource<Int, User>
    
    }
    
  • 网络数据源,例如从网络获取一个app下载列表:

    class NetworkPagingSource : PagingSource<Int, AppData>() {
    
        override fun getRefreshKey(state: PagingState<Int, AppData>): Int? {
            return state.anchorPosition?.let { anchorPosition ->
                val anchorPage = state.closestPageToPosition(anchorPosition)
                anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1)
            }
        }
    
        override suspend fun load(params: LoadParams<Int>): LoadResult<Int, AppData> {
            //当前页
            val currentPage = params.key ?: 1
            //上一页
            val prevKey: Int? = if (currentPage == 1) null else currentPage - 1
            //下一页,这里只加载50页,可根据业务情况修改
            val nextKey: Int? = if (currentPage < 50) currentPage + 1 else null
    
            return try {
                //从网络获取数据
                val result = NetworkService.rankAppListApi.getAppList(currentPage)
                if (result?.data != null) {
                    LoadResult.Page(
                        data = result.data,
                        prevKey = prevKey,
                        nextKey = nextKey
                    )
                } else {
                    LoadResult.Error(IllegalStateException("数据为空"))
                }
            } catch (e: Exception) {
                LoadResult.Error(e)
            }
        }
    
    }
    
3.2 Pager 数据流

上面的PageSource是数据源,Pager则是提供的方法可显示来自 PagingSourcePagingData 对象的响应式流。Paging 库支持使用多种流类型,包括 FlowLiveData 以及 RxJava 中的 FlowableObservable 类型。

通常在ViewModel中创建一个Pager流,

fun loadNews(): Flow<PagingData<AppData>> {
        return Pager(
            config = PagingConfig(
                pageSize = 20//每页加载20个数据
            ),
            pagingSourceFactory = { NetworkPagingSource() }//设置数据源
        ).flow.cachedIn(viewModelScope)//缓存在ViewModelScope作用域中
    }

数据接收,在Activity中接收数据:

lifecycleScope.launchWhenCreated {
            mViewModel.loadNews().collectLatest {
                LogUtils.d("receive===$it")
               
            }
        }

3.3 PagingDataAdapter数据显示适配器

上面已定义了数据源,数据流,那怎样将数据显示出来呢?需要使用PagingDataAdapter,pagingAdapter调用submitData(pagingData)方法将数据显示到RecyclerView容器中。

pagingAdapter.submitData(pagingData)

4. 分页显示数据库数据示例

4.1 导包
	//协程
	implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1"
    implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1'
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1"
    implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.1"
    implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
    implementation "com.blankj:utilcodex:1.29.0"
	//paging
    implementation "androidx.paging:paging-runtime-ktx:3.0.0"
    testImplementation "androidx.paging:paging-common-ktx:3.0.0"
    implementation "androidx.activity:activity-ktx:1.1.0"
    implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"

    //room包
    implementation "androidx.room:room-runtime:2.4.0-alpha04"
    kapt "androidx.room:room-compiler:2.4.0-alpha04"
    testImplementation "androidx.room:room-testing:2.4.0-alpha04"
    //room使用协程
    implementation "androidx.room:room-ktx:2.2.4"
4.2 数据库与PagingSource

User实例类

@Entity(tableName = "user")
class User {
    @Ignore
    constructor(id: Int) {
        this.id = id
    }


    @Ignore
    constructor(firstName: String?, age: Int) {
        this.firstName = firstName
        this.age = age
    }
    constructor(id: Int, firstName: String?, age: Int) {
        this.id = id
        this.firstName = firstName
        this.age = age
    }
    @PrimaryKey
    @ColumnInfo(name = "id")
    var id: Int = 0

    @ColumnInfo(name = "first_name", typeAffinity = ColumnInfo.TEXT)
    var firstName: String? = null

    @ColumnInfo(name = "age", typeAffinity = ColumnInfo.INTEGER)
    var age: Int = 0
}

UserDao类提供PagingSource

@Dao
interface UserDao {

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    @Transaction
    fun insertUsers(users: List<User>)

	//PagingSource数据源
    @Query("SELECT * FROM user ORDER BY id COLLATE NOCASE ASC")
    fun allUserById(): PagingSource<Int, User>

}

UserDataBase类

@Database(
    entities = [User::class],
    version = 1,
    exportSchema = false,
)
abstract class UserDataBase : RoomDatabase() {


    companion object {
        private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)

        private var INSTANCE: UserDataBase? = null

        fun init(context: Context) {
            if (INSTANCE == null) {
                synchronized(UserDataBase::class.java) {
                    if (INSTANCE == null) {
                        create(context)
                    }
                }
            }
        }
		//创建数据库
        private fun create(context: Context) {
            INSTANCE =
                Room.databaseBuilder(context, UserDataBase::class.java, "user_db")
                    .allowMainThreadQueries()
                    .fallbackToDestructiveMigration()
                    .addCallback(object : RoomDatabase.Callback() {
                        override fun onCreate(db: SupportSQLiteDatabase) {
                            fillInDb(context.applicationContext)
                        }
                    }).build()

        }


        fun getDB(): UserDataBase {
            return INSTANCE ?: throw NullPointerException("UserDataBase 未初始化")
        }

        private fun fillInDb(context: Context) {
            coroutineScope.launch {
                getDB().getUserDao().insertUsers(getUserList())
            }
        }
		//插入测试的5000条数据
        private fun getUserList(): List<User> {
            val userList = mutableListOf<User>()
            for (id in 1..5000) {
                userList.add(User(id, "userName_$id", age = id))
            }
            return userList
        }
    }
    //增加抽象方法,Room框架会自动实现这个方法
    abstract fun getUserDao(): UserDao

}
4.3 Pager

定义在MainViewModel中

class MainViewModel : ViewModel() {
    private val userDao = UserDataBase.getDB().getUserDao()


    val allUsers = Pager(
        PagingConfig(
            pageSize = 50,
            enablePlaceholders = true,
            maxSize = 200
        )
    ) {
        userDao.allUserById()
    }.flow.cachedIn(viewModelScope)

}
4.4 PagingDataAdapter

UserAdapter适配器类:

class UserAdapter : PagingDataAdapter<User, UserAdapter.UserViewHolder>(
    object : DiffUtil.ItemCallback<User>() {
        override fun areItemsTheSame(oldItem: User, newItem: User): Boolean {
            return oldItem.id == newItem.id
        }

        @SuppressLint("DiffUtilEquals")
        override fun areContentsTheSame(oldItem: User, newItem: User): Boolean {
            return oldItem == newItem
        }

    }
) {


    class UserViewHolder(val binding: UserItemBinding) : RecyclerView.ViewHolder(binding.root)

    override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
        getItem(position)?.let { user ->
            holder.binding.userId.text = "id_${user.id}"
            holder.binding.userName.text = "${user.firstName}"
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
        val binding = UserItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return UserViewHolder(binding)
    }

}
4.5 Pager与PagingDataAdapter绑定
class MainActivity : AppCompatActivity() {
    private val dataBinding by lazy {
        ActivityMainBinding.inflate(layoutInflater)
    }

    private val viewModel by viewModels<MainViewModel>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(dataBinding.root)
		//recyclerView与UserAdapter绑定
        dataBinding.recyclerView.layoutManager = LinearLayoutManager(this)
        val userAdapter = UserAdapter()
        dataBinding.recyclerView.adapter = userAdapter
		//监听数据
        lifecycleScope.launchWhenResumed {
            @OptIn(ExperimentalCoroutinesApi::class)
            viewModel.allUsers.collectLatest { userAdapter.submitData(it) }//将数据提交到Adapter中
        }

        lifecycleScope.launch {
            //监听数据加载的变化
            userAdapter.loadStateFlow.collectLatest { state ->
                LogUtils.d("state==$state")
            }
        }

    }
}
4.6 效果

在这里插入图片描述

4.7 示例代码

代码链接

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值