Android Kotlin Paging3 Flow完整教程

Posted 安果移不动

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android Kotlin Paging3 Flow完整教程相关的知识,希望对你有一定的参考价值。

准备好接口

package com.example.android_learn_paging.net

import com.example.android_learn_paging.model.NetDataList
import retrofit2.http.GET
import retrofit2.http.Query

interface FeedBackApi 
    @GET("api/v1/open/test")
    suspend fun getFeedBack(
        @Query("page") page: Int,
        @Query("size") size: Int
    ): NetDataList

用laravel8.5 写的 就是对数据库进行分页查询

    public function index($size, $search): JsonResponse
    
        $res = AppFeedBack::query();
        if ($search) 
            if (isset($search['app_name'])) 
                $res = $res->where('app_name', 'like', "%" . $search['app_name'] . "%");
            
            if (isset($search['content'])) 
                $res = $res->where('content', 'like', "%" . $search['content'] . "%");
            
        
        $data = $res
            ->orderBy('create_time', 'desc')
            ->paginate($size)
            ->toArray();

//       return  AppFeedBack::simplePaginate(15);

        return $this->apiSuccess("", $data);
    

大概给大家看下工程结构

很复杂。

 返回的数据bean格式如下

package com.example.android_learn_paging.model

data class FeedBack(
    val id: Int, val app_name: String, val package_name: String, val content: String
)

data class FeedBacks(
    val current_page: Int, val data: ArrayList<FeedBack>, val last_page: Int

)

data class NetDataList(
    val code: Int, val message: String, val data: FeedBacks
)

api接口

package com.example.android_learn_paging.net

import com.example.android_learn_paging.model.NetDataList
import retrofit2.http.GET
import retrofit2.http.Query

interface FeedBackApi 
    @GET("api/v1/open/test")
    suspend fun getFeedBack(
        @Query("page") page: Int,
        @Query("size") size: Int
    ): NetDataList

初始化数据类

package com.example.android_flow_practice.net

import android.util.Log
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.create

object RetrofitClient 
    val url = "https://xxx.xxxx.xxxx";
    private const val TAG = "RetrofitClient"

    private val instance: Retrofit by lazy 
        val interceptor = HttpLoggingInterceptor(HttpLoggingInterceptor.Logger 
            Log.w(TAG, "$it")
        )

        interceptor.level = HttpLoggingInterceptor.Level.BODY;

        Retrofit.Builder().client(
            OkHttpClient.Builder().addInterceptor(interceptor).build()
        ).baseUrl(url)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    


    fun <T> createApi(clazz: Class<T>): T 
        return instance.create(clazz) as T
    

用到了三方依赖


    implementation "androidx.activity:activity-ktx:1.5.1"
    implementation "androidx.fragment:fragment-ktx:1.5.2"

    implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.5.1"

    implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"

    implementation "com.squareup.retrofit2:retrofit:2.9.0"
    implementation "com.squareup.retrofit2:converter-gson:2.9.0"
    implementation "com.squareup.okhttp3:logging-interceptor:3.4.1"

    implementation "androidx.paging:paging-runtime-ktx:3.1.1"
    implementation "com.squareup.picasso:picasso:2.71828"

 

 这俩勾上 kapt加上

好了 

继续

AppConfigs
package com.example.android_learn_paging.config

object AppConfigs 
     const val LOAD_PAGE = 8

    //默认就是3倍
     const val INITPAGE_SIZE = LOAD_PAGE * 3

paging3 的一次加载页数 。这里写死了。为啥要这么写 因为后面就知道了

核心adapter PageSource

FeedBackPagingSource
package com.example.android_learn_paging.paging

import android.util.Log
import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.example.android_flow_practice.net.RetrofitClient
import com.example.android_learn_paging.config.AppConfigs
import com.example.android_learn_paging.model.FeedBack
import com.example.android_learn_paging.net.FeedBackApi
import kotlinx.coroutines.delay

class FeedBackPagingSource : PagingSource<Int, FeedBack>() 
    private val TAG = "FeedBackPagingSource"

    /**
     * 1 8
     * 2 8
     * 3  8
     *
     *
     * null 2
     * 1 3
     * 2 4
     *
     *
     *  1 24
     * 2 8
     * 3  8
     *
     *
     * null 4
     * 3 5
     * 4 6
     */
    override suspend fun load(params: LoadParams<Int>): LoadResult<Int, FeedBack> 
        val currentPage = params.key ?: 1
        val pageSize = params.loadSize
        val feedbacks =
            RetrofitClient.createApi(FeedBackApi::class.java).getFeedBack(currentPage, pageSize)

        Log.d(TAG, "load: currentPage $currentPage ,pageSize $pageSize")


        var prevKey: Int? = null
        var nextKey: Int? = null
        if (currentPage == 1) 
            prevKey = null
            nextKey = AppConfigs.INITPAGE_SIZE / AppConfigs.LOAD_PAGE + 1
         else 
            prevKey = currentPage - 1
            nextKey =
                if (feedbacks.data.last_page > feedbacks.data.current_page) currentPage + 1 else null
        






        return try 
            LoadResult.Page(data = feedbacks.data.data, prevKey = prevKey, nextKey = nextKey)
         catch (e: Exception) 
            e.printStackTrace()
            return LoadResult.Error(e)
        

    

    override fun getRefreshKey(state: PagingState<Int, FeedBack>): Int? 
        return null
    

 由于第一次加载了三页数 所以再次加载不可以直接从2开始

 这个注释上面是指的

第一次 加载8个  对应下面  首次加载为null 然后下一页为2

第二次  8个  对应下面  第二次 加载  上一页为 1 然后下一页为3 、

这都是理想情况下 当如果是分页首次加载了 initialLoadSize  为默认3倍的时候要处理就往下面数

就懂了

viewModel记录了我们在如何处理这个数据 并且返回了FLow

package com.example.android_learn_paging.viewmodel

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.cachedIn
import com.example.android_learn_paging.config.AppConfigs
import com.example.android_learn_paging.model.FeedBack
import com.example.android_learn_paging.paging.FeedBackPagingSource
import kotlinx.coroutines.flow.Flow

class FeedBackViewModel : ViewModel() 


    private val moives by lazy 
        Pager(config = PagingConfig(
            pageSize = AppConfigs.LOAD_PAGE,
            initialLoadSize = AppConfigs.INITPAGE_SIZE,
            prefetchDistance = 1
        ), pagingSourceFactory =  FeedBackPagingSource() ).flow.cachedIn(viewModelScope)
    

    fun loadFeedBack(): Flow<PagingData<FeedBack>> = moives

下面就是页面代码了。

先 MainActivity吧

package com.example.android_learn_paging.activity

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.activity.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.paging.LoadState
import com.example.android_learn_paging.adapter.FeedBackAdapter
import com.example.android_learn_paging.adapter.MovieLoadMoreAdapter
import com.example.android_learn_paging.databinding.ActivityMainBinding
import com.example.android_learn_paging.viewmodel.FeedBackViewModel
import kotlinx.coroutines.flow.collectLatest

class MainActivity : AppCompatActivity() 

    private val feedBackViewModel: FeedBackViewModel by viewModels()

    private val mBinding: ActivityMainBinding by lazy 
        ActivityMainBinding.inflate(layoutInflater)
    

    override fun onCreate(savedInstanceState: Bundle?) 
        super.onCreate(savedInstanceState)
        setContentView(mBinding.root)

        val feedBackAdapter = FeedBackAdapter(this)

        mBinding.apply 
            rv.adapter =
                feedBackAdapter.withLoadStateFooter(MovieLoadMoreAdapter(this@MainActivity))

            swipeRefreshLayout.setOnRefreshListener 
                feedBackAdapter.refresh()

            
        

        lifecycleScope.launchWhenCreated 
            feedBackViewModel.loadFeedBack().collectLatest 
                feedBackAdapter.submitData(it)
            
        

        lifecycleScope.launchWhenCreated 
            feedBackAdapter.loadStateFlow.collectLatest  status ->
                mBinding.swipeRefreshLayout.isRefreshing = status.refresh is LoadState.Loading
            
        
    

布局

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".activity.MainActivity">

    <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
        android:id="@+id/swipeRefreshLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">


        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/rv"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
    </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

看到了很多生代码 没关系 一点一点补充

FeedBackAdapter.kt
package com.example.android_learn_paging.adapter

import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.DiffUtil
import com.example.android_learn_paging.databinding.ItemPagingBinding
import com.example.android_learn_paging.model.FeedBack

val diffUtil = object : DiffUtil.ItemCallback<FeedBack>() 
    //如果id一样 就认为是同一个元素
    override fun areContentsTheSame(oldItem: FeedBack, newItem: FeedBack): Boolean 
        return oldItem.id == newItem.id
    

    override fun areItemsTheSame(oldItem: FeedBack, newItem: FeedBack): Boolean 
        return oldItem == newItem
    


class FeedBackAdapter(private val context: Context) :
    PagingDataAdapter<FeedBack, BindingViewHolder>(diffUtil) 
    override fun onBindViewHolder(holder: BindingViewHolder, position: Int) 
        val feedBack = getItem(position)
        feedBack?.let 
            val binding = holder.binding as ItemPagingBinding
            binding.feedback = it
            binding.packageName = "https://www.baidu.com/s?wd=%E7%99%BE%E5%BA%A6%E7%83%AD%E6%90%9C&sa=ire_dl_gh_logo_texing&rsv_dl=igh_logo_pc";
        
    

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BindingViewHolder 
//        val binding = Pagint
        val binding = ItemPagingBinding.inflate(LayoutInflater.from(context), parent, false)
        return BindingViewHolder(binding)
    
BindingViewHolder
package com.example.android_learn_paging.adapter

import androidx.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding

class BindingViewHolder(val binding: ViewBinding) : RecyclerView.ViewHolder(binding.root) 
ImageViewBindingAdapter

 对app:image标签来进行注解

package com.example.android_learn_paging.adapter

import android.graphics.Color
import com.example.android_learn_paging.R
import android.text.TextUtils
import android.widget.ImageView
import androidx.databinding.BindingAdapter
import com.squareup.picasso.Picasso

class ImageViewBindingAdapter 


    companion object 
        @JvmStatic
        @BindingAdapter("image")
        fun setImage(imageView: ImageView, url: String) 
            if (!TextUtils.isEmpty(url)) 
                Picasso.get().load(url).placeholder(R.drawable.ic_launcher_background)
                    .into(imageView)
             else 
                imageView.setBackgroundColor(Color.GRAY)
            
        
    

 下拉加载更多组件

MovieLoadMoreAdapter
package com.example.android_learn_paging.adapter

import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.paging.LoadState
import androidx.paging.LoadStateAdapter
import com.example.android_learn_paging.databinding.FeedbackLoadmoreBinding

class MovieLoadMoreAdapter(private val context: Context) : LoadStateAdapter<BindingViewHolder>() 
    override fun onBindViewHolder(holder: BindingViewHolder, loadState: LoadState) 

    

    override fun onCreateViewHolder(parent: ViewGroup, loadState: LoadState): BindingViewHolder 
        val binding = FeedbackLoadmoreBinding.inflate(LayoutInflater.from(context), parent, false)
        return BindingViewHolder(binding)
    

adapter非常简单

feedback_loadmore.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:padding="10dp">

    <ProgressBar
        android:id="@+id/progressBar"
        android:layout_width="20dp"
        android:layout_height="20dp"
        android:layout_marginStart="20dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <TextView
        android:id="@+id/tv_loading"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="加载更多数据"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="@id/progressBar" />

</androidx.constraintlayout.widget.ConstraintLayout>

 adapter item布局

item_paging.xml
<?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"
    xmlns:tools="http://schemas.android.com/tools">

    <data>

        <variable
            name="packageName"
            type="String" />

        <variable
            name="feedback"
            type="com.example.android_learn_paging.model.FeedBack" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <TextView
            android:id="@+id/tv_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="20dp"
            android:text="@feedback.app_name"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/tv_context"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:text="@feedback.content"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/tv_name" />

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            app:image="@packageName"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/tv_context" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

 系统的adapter指向一个上滑加载更多

下拉刷新用的是

 

 检测刷新状态 并且刷新代码

是的 

PagingDataAdapter 有自己的刷新代码。。而我是刚知道。。我以前都去刷新

 

他 先通过一个成员变量引用出来 然后调用

.invalidate()

进行刷新 也是可行的。。。但是不知道会不会有什么潜在问题。

以上是关于Android Kotlin Paging3 Flow完整教程的主要内容,如果未能解决你的问题,请参考以下文章

Android Jetpack Kotlin/Java pageing3的基础使用。

Android-利用Jetpack-Compose-+Paging3+swiperefresh实现分页加载,下拉上拉效果

Jetpack ComposeLazyColumn 使用Paging3分页+SwipeRefresh下拉刷新

Android Paging3 Footer踩坑优化

官方推荐 Flow 取代 LiveData,有必要吗?

将 Paging 3 alpha 更新为稳定导致索引问题 Android