preface

1. Must PagingDataAdapter be inherited to use Paging? 2. Use AsyncPagingDataDiffer to implement the Paging function example 3. Furthermore, the decorative mode is used to realize the Paging function

If you find this article helpful, please give it a thumbs up. Thank you

usePagingMust inheritPagingDataAdapter?

Paging3 has recently released a beta version, I believe that a few months should be officially released using Paging3 can be more convenient to simplify the loading of more logic, can be more easily in RecyclerView gradually properly load data

Paging3 must be inherited from PagingAdapter. This is actually wrong. If we click on the source of PagingDataAdapter, You can see that AsyncPagingDataDiffer does most of its work

The main component of Paging library in the interface layer is PagingDataAdapter, which is a RecyclerView adapter to process Paging data. Alternatively, you can use the accompanying AsyncPagingDataDiffer component to build your own custom adapters.

In actual development, our Adapter often has its own base class, and it is not convenient to inherit PagingDataAdapter, so it is a more practical choice to build your own custom adapter based on AsyncPagingDataDiffer in actual development

useAsyncPagingDataDifferCustom adapter

1. Look at the original firstBaseAdapter

We usually already have Adapter base classes in our projects, so let’s define a simple one

abstract class BaseAdapter<T:DifferData,VH:RecyclerView.ViewHolder> :RecyclerView.Adapter<VH>() {
    protected var mDataList = mutableListOf<T>()

    fun setDataList(dataList:List<T>){
        mDataList = dataList.toMutableList()
        notifyDataSetChanged()
    }

    override fun getItemCount(a): Int {
        return mDataList.size
    }
}
Copy the code

The BaseAdapter we define here is very simple, just for the purpose of simulation, mainly support setDataList functionality, and nothing else

2. CustomPagingAdapter

We can customize the PagingAdapter to realize the Paging function and inherit it from the BaseAdapter without affecting the original function

abstract class PagingAdapter<T:DifferData,VH:RecyclerView.ViewHolder> : BaseAdapter<T,VH>() {
    private val differ = AsyncPagingDataDiffer<T>(
        diffCallback =DifferCallback(),
        updateCallback = AdapterListUpdateCallback(this),
        mainDispatcher = Dispatchers.Main,
        workerDispatcher = Dispatchers.Default
    )

    init {
    	// Listen for data and assign a value to BaseAdapter after loading successfully
        differ.addLoadStateListener {
            if (it.append is LoadState.NotLoading) {
                val items = differ.snapshot().items
                setDataList(items)
            }
        }
    }

    suspend fun submitList(pagingData: PagingData<T>) {
        differ.submitData(pagingData)
    }

    override fun onBindViewHolder(holder: VH, position: Int) {
    	// This step is necessary because Paging is preloaded via getItem
        differ.getItem(position)
    }
}
Copy the code

The PagingAdapter mainly does the following: 1. Define differ for subsequent submitList updates to use 2. Provide the submitList method to update data 3. Call getItem in onBindViewHolder. This step is essential because Paging preloads the next page through getItem. Listen to the loading status and call setDataList to assign a value to BaseAdapter after the loading is successful

3. Use customPagingAdapter

When we want to develop a new Adapter, we simply inherit the PagingAdapter

class DemoAdapter:PagingAdapter<NewsBean.StoriesBean,DemoAdapter.ViewHolder>() {
    class ViewHolder(itemView:View):RecyclerView.ViewHolder(itemView)

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        return ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_news,parent,false))}override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        super.onBindViewHolder(holder, position)
        holder.itemView.run {
            val data = mDataList[position]
            tv_title.text = data.title
            Glide.with(context).load(data.images? .get(0)).into(iv_cover)
        }
    }
}
Copy the code

DemoAdapter, which originally inherits from BaseAdapter, almost does not need to modify the code, but inherits from PagingAdapter, which realizes the enhancement of the function and supports the automatic preloading function

Achieve the decorative mode to achieve the Paging function

The AsyncPagingDataDiffer custom adapter inherits from BaseAdapter and implements enhancements. What’s wrong with this?

1. What’s wrong with inheritance?

The methods used above are essentially inherited

  • Increased levels of inheritance affect the maintainability of code

    We are inBaseAdapterwithDemoAdapterIn between, another layer is addedPagingAdapterIf we add new features later, we may need to add new onesAdapterBase classes, which are difficult to maintain
  • The implementation of the subclass depends on the implementation of the parent class, destroying the encapsulation of the class. If we want to figure out what methods and attributes the subclass has, we must follow up to read the code of the parent class, and the implementation of the subclass depends on the implementation of the parent class. If the parent class is modified, it will affect the logic of all the subclasses and bring unpredictable bugs

2. Use decoration

1. Define one firstPagingWrapAdapter

class PagingWrapAdapter<T : DifferData, VH : RecyclerView.ViewHolder>(
    private val innerAdapter: RecyclerView.Adapter<VH>,
    private val callback: ((List<T>) -> Unit)
) : RecyclerView.Adapter<VH>() {
    private val differ = AsyncPagingDataDiffer<T>(
        diffCallback = DifferCallback(),
        updateCallback = AdapterListUpdateCallback(this),
        mainDispatcher = Dispatchers.Main,
        workerDispatcher = Dispatchers.Default
    )

    init {
        differ.addLoadStateListener {
            if (it.append is LoadState.NotLoading) {
                val items = differ.snapshot().items
                callback.invoke(items)
            }
        }
    }

    suspend fun submitList(pagingData: PagingData<T>) {
        differ.submitData(pagingData)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
        return innerAdapter.onCreateViewHolder(parent, viewType)
    }

    override fun onBindViewHolder(holder: VH, position: Int) {
        differ.getItem(position)
        innerAdapter.onBindViewHolder(holder, position)
    }

    override fun getItemCount(a): Int {
        return innerAdapter.itemCount
    }
}
Copy the code

PagingWrapAdapter mainly do the following work 1. Inherit in RecyclerView.Adapter, and achieve several default methods 2. 2. Pass in an innerAdapter and implement the above methods by its agent

The UML diagram is as follows:

2. Call method

private val mAdapter by lazy {
    val readAdapter = Demo2Adapter()
    PagingWrapAdapter<NewsBean.StoriesBean, Demo2Adapter.ViewHolder>(readAdapter) {
        readAdapter.setDataList(it)
    }
}
Copy the code

As you can see, the original Adapter does not need any modification, just need to pass into the PagingWrapAdapter, to implement the preload function

conclusion

Since adapters in our real projects often already have base classes, it is not convenient to inherit PaginDataAdapter and build custom adapters based on AsyncPagingDataDiffer is a better choice

Inheritance will also bring problems such as maintainability. A better choice is to use decoration mode to enhance functions to achieve the goal of realizing Paging function without modifying the original code

Show Me The Code

All code in this article is visible: PagingAdapter

This article is participating in the “Nuggets 2021 Spring Recruitment Campaign”, click to see the details of the campaign