1---分页思路_探索分页3-第1部分

1---分页思路

本文的重点 (Takeaway from this article)

In this article, you’ll learn how and why we need to use Paging3 — the jetpack library. You’ll also learn how it is different from its previous versions. You’ll also find a detailed guide on how to implement paging3 in MVVM architecture using Spotify API.

在本文中,您将了解我们如何以及为什么需要使用Paging3(jetpack库)。 您还将学习它与以前的版本有何不同。 您还将找到有关如何使用Spotify API在MVVM体系结构中实现页面调度3的详细指南。

A GitHub repository with a working sample of including all the things that I discussed here is linked at the bottom of this article. Feel free to play with it.

本文底部链接了一个GitHub存储库,其中包含一个工作示例,其中包括我在此处讨论的所有内容。 随意玩。

介绍 (Introduction)

Paging is a Jetpack library designed to help developers load & display pages of data from massive datasets with minimal use of network and system resources.

Paging是一个Jetpack库,旨在帮助开发人员在不使用网络和系统资源的情况下,从大量数据集中加载和显示数据页面。

The paging library can load the data from a remote source or local storage. Offline mode is essential for effective user experience; it’s recommended to get data from a remote source and save it locally. Then use the local storage as a single source of truth. Paging3 library covers all of this out of the box.

分页库可以从远程源或本地存储中加载数据。 离线模式对于有效的用户体验至关重要。 建议从远程来源获取数据并将其保存在本地。 然后将本地存储用作单个事实来源。 Paging3库提供了所有现成的功能。

In addition to that, paging3 components are built to fit in the recommended Android App Architecture and also to provide better Kotlin support.

除此之外,paging3组件被构建为适合推荐的Android App Architecture并提供更好的Kotlin支持。

与以前的版本有何不同? (How is it different from previous versions?)

One of the key enhancement from its previous versions of paging is that now it’s a Kotlin first library with Flow API’s power from the repository to UI components.

以前的页面调度版本的主要增强功能之一是,它现在是Kotlin的第一个库,具有从存储库到UI组件的Flow API的功能。

One of the significant improvements is that we can add a custom footer, header, loading, and retry items to the paging list with less boilerplate code. We can also use filter, map, and other operators to apply necessary transformations before displaying the data.

一项重大改进是,我们可以使用更少的样板代码将自定义页脚,页眉,加载和重试项添加到分页列表中。 我们还可以使用过滤器,地图和其他运算符在显示数据之前应用必要的转换。

To integrate the paging library seamlessly with recommended architectures, we have various paging components such as PagingConfig, PagingSource, LoadResult, PagingDataAdapter, and more. You’ll learn about them in the following sections of this article.

为了将分页库与推荐的架构无缝集成,我们提供了各种分页组件,例如PagingConfigPagingSourceLoadResult PagingDataAdapter等。 您将在本文的以下各节中了解它们。

积分 (Integration)

At the time I was writing this article, the paging3 library is still in the alpha stage. So I recommend you check for the latest version if you’re from the future 😉.

在撰写本文时,paging3库仍处于alpha阶段。 因此,如果您来自未来I,我建议您检查最新版本。

Integrating paging V3 is as simple as integrated any other Jetpack library. You need to add the following line under the dependencies tag in the app level build.gradle file.

集成分页V3与集成任何其他Jetpack库一样简单。 您需要在应用程序级别build.gradle文件中的dependencies标记下添加以下行。

implementation "androidx.paging:paging-runtime:3.0.0-alpha03"

分页源 (PagingSource)

Loading large sets of data from a remote source or local storage is an expensive operation. PagingSource is a component from the paging library to handle this in the best way possible.

从远程源或本地存储加载大量数据是一项昂贵的操作。 PagingSource 是分页库中的组件,用于以最佳方式处理此问题。

The importance of PagingSource lies in the generics of its parameters. It has two parameters: Key and value. The datatype of both parameters is Any. The value represents the data class of the paging list item.

PagingSource的重要性 在于其参数的泛型。 它具有两个参数: Keyvalue 。 两个参数的数据类型均为Any 。 该值表示分页列表项的数据类。

The Key is another parameter to define what data to load. As its datatype is Any, we can use it to represent a page number or the number of items per page or a custom type that your server supports. This type of data loading opens the gates of the paging library to a wide range of developers.

Key是另一个参数,用于定义要加载的数据。 由于其数据类型为Any ,我们可以使用它来表示页码或每页的项目数或服务器支持的自定义类型。 这种类型的数据加载为许多开发人员打开了分页库的大门。

Enough with the talk, let’s do some coding. To build a PagingSource, we need a model class and an API request. Here I used Spotify API with Retrofit. I’m not going to discuss the setup of Retrofit and API, but you can find it in the GitHub repository linked at the bottom.

讲完了,让我们做一些编码。 要构建PagingSource 我们需要一个模型类和一个API请求。 在这里,我将Spotify API与Retrofit一起使用。 我不会讨论Retrofit和API的设置,但是您可以在底部链接的GitHub存储库中找到它。

Assuming you’ve set up network client and API endpoints, the next step is to design Spotify paging index. Spotify API doesn’t follow an incremental page number-based results, but they use offset and limit to retrieve the server’s data.

假设您已经设置了网络客户端和API终结点,下一步就是设计Spotify分页索引。 Spotify API不会遵循基于增量页码的结果,但是它们使用offset和limit来检索服务器的数据。

Spotify分页如何工作? (How does Spotify Paging work?)

Take the following URL as an example:

以以下URL为例:

https://api.spotify.com/v1/browse/categories?country=IN&limit=5&offset=10

In this example, in a list of 42(total) categories from Spotify API: From the tenth(offset) category, retrieve the next five(limit) categories.

在此示例中,在Spotify API的42个( total )类别列表中:从第十个( offset )类别中检索下五个( limit )类别。

设计Spotify分页逻辑 (Design a Spotify paging logic)

It’s easy; we need to increase the offset for the next paging request with the limit size after completing the present request. Because it’s not based on just page number, I created a custom data class to maintain necessary indices in the PagingSource.Have a look:

这很容易; 在完成当前请求后,我们需要使用限制大小来增加下一个寻呼请求的偏移量。 因为它不仅仅基于页码,所以我创建了一个自定义数据类来维护PagingSource必要索引 看一看:

data class SptifyPagingIndex(val limit : Int, val offest : Int, val total : Int)


fun paggingIndexing(offest : Int, totalFromServer : Int,
                    totalFromPagging : Int  ): Pair<SptifyPagingIndex?,SptifyPagingIndex?> 
    var nextKeyOffest : Int? = offest + loadSize
    if (offest != 0 && offest >= totalFromPagging){
        nextKeyOffest = null
    }
    var previousKeyOffest : Int? = offest
    if (offest == 0){
        previousKeyOffest = null
    }
    val previousKey = previousKeyOffest?.let {
        SptifyPagingIndex(limit= loadSize,offest = it ,total = totalFromServer)
    }
    val nextKey = nextKeyOffest?.let {
        SptifyPagingIndex(limit= loadSize,offest = it,total = totalFromServer)
    }
    return Pair(previousKey, nextKey)
}

Finally, we’re here; it’s time to implement the PagingSource the core part of the paging library. Have a look:

最后,我们在这里; 是时候实现PagingSource 分页库的核心部分。 看一看:

private const val loadSize = 5
private val INTIAL_SPOTIFY_PAGE_INDEX = SptifyPagingIndex(limit = loadSize,offest = 0,total = 0)


class SpotifyPagingSource(private val spotifyAPI: SpotifyAPI)
      : PagingSource<SptifyPagingIndex, CategoryItem>() {


    override suspend fun load(params: LoadParams<SptifyPagingIndex>): LoadResult<SptifyPagingIndex, CategoryItem> {
        val limit = params.key?.limit ?: INTIAL_SPOTIFY_PAGE_INDEX.limit
        val offest = params.key?.offest ?: INTIAL_SPOTIFY_PAGE_INDEX.offest
          
        return try {
              
            val response = spotifyAPI.getCategories("IN","$limit","$offest")
            val categories = response.categories?.items?:ArrayList()
            val (previousKey, nextKey) = paggingIndexing(offest = offest,
                totalFromServer = response.categories?.total?:0,
                totalFromPagging = params.key?.total?:0)


            LoadResult.Page(
                data = categories,
                prevKey = previousKey,
                nextKey =nextKey)
              
        } catch (exception: IOException) {
            return LoadResult.Error(exception)
        } catch (exception: HttpException) {
            return LoadResult.Error(exception)
        }
    }
}

If we’re requesting for the first time, we pick limit and offset values from INTIAL_SPOTIFY_PAGE_INDEX else we choose them from the LoadParams provided by SpotifyPagingSource.

如果是第一次请求,则从INTIAL_SPOTIFY_PAGE_INDEX选择limitoffset值,否则我们从LoadParams提供的SpotifyPagingSource选择它们。

spotifyAPI is a retrofit API interface that we used to invoke categories request. Once we received the response, we’ve to update the previous and next keys, which SpotifyPagingSource will provide them as LoadParams for the next request. Then we can post the list and keys to LoadResult sealed class to use them on the other side(ViewModel).

spotifyAPI是我们用来调用类别请求的改造API接口。 收到响应后,我们必须更新上一个和下一个键, SpotifyPagingSource会将这些键作为下一个请求的LoadParams提供。 然后,我们可以将列表和键发布到LoadResult密封的类,以在另一端使用它们(ViewModel)。

资料库 (Repository)

Now that we’re done with pagingsource, the next step is to invoke it from our repository. We do this by using Pager, a primary entry point into Paging, the constructor for a reactive stream of PagingData.

既然我们已经完成了pagingsource ,那么下一步就是从我们的存储库中调用它。 我们通过使用Pager实现此目的, Pager是Paging的主要入口,它是PagingData的React流的PagingData

It has two mandatory parameters: config and pagingSourceFactory.

它具有两个必填参数:config和pagingSourceFactory

分页配置 (PagingConfig)

An object used to configure loading behavior within a Pager, as it loads content from a PagingSource. We can configure various attributes such as

一个对象,用于配置Pager中的加载行为,因为该对象从PagingSource加载内容。 我们可以配置各种属性,例如

  • Initial load count

    初始负荷计数
  • Whether to enable place holders while loading

    加载时是否启用占位符
  • Prefetch distance, which defines how far from the edge of loaded content access, must be to trigger further loading.

    预取距离必须定义为距离加载的内容访问边缘多远,才能触发进一步的加载。
  • PaseSize is used to define the number of items to be fetched from PagingSource at once.

    PaseSize用于定义要从PagingSource一次获取的项目数。

class SpotifyRepo(private val spotifyAPI: SpotifyAPI) {


    fun getPaggingCategories() : Flow<PagingData<CategoryItem>> {
        return Pager(
            config = PagingConfig(pageSize = loadSize,
                enablePlaceholders = false),
            pagingSourceFactory = { SpotifyPagingSource(spotifyAPI) }
        ).flow
    }


}

We’ve given page size of five and disabled placeholders for now, via pagingSourceFactory parameter we need to pass SpotifyPagingSource. Finally, we need to convert into the flow to observe it from the view-model or UI components.

现在,我们已经给页面大小指定了五个,并禁用了占位符,通过pagingSourceFactory参数,我们需要传递SpotifyPagingSource 。 最后,我们需要转换为流以从视图模型或UI组件进行观察。

视图模型 (ViewModel)

Now that we’ve set up Pager in the repository, the next step is to invoke it from the view-model. To ensure the usage of flow restricted to ViewModel, we used cached function on flow with view-model scope. Have a look:

现在我们已经在存储库中设置了Pager,下一步是从视图模型中调用它。 为了确保使用仅限于ViewModel的流,我们在具有view-model范围的流上使用了cached功能。 看一看:

class CategoriesViewModel(val spotifyRepo: SpotifyRepo) : ViewModel() {


    val job = Job()
    val scope = CoroutineScope(job + Dispatchers.IO)
    private var currentSearchResult: Flow<PagingData<CategoryItem>>? = null


    fun searchRepo(): Flow<PagingData<CategoryItem>> {
        val lastResult = currentSearchResult
        if (lastResult != null) {
            return lastResult
        }
        val newResult: Flow<PagingData<CategoryItem>> = spotifyRepo.getPaggingCategories()
            .cachedIn(scope)
        currentSearchResult = newResult
        return newResult
    }


    override fun onCleared() {
        super.onCleared()
        scope.coroutineContext.cancelChildren()
    }


}

PagingDataAdapter (PagingDataAdapter)

It’s time to implement the adapter, here we use PagingDataAdapter from the paging3 library which extends RecyclerView.Adapter. It’s quite similar to implement the Recyclerview adapter. Have a look:

它的时间来执行适配器,这里我们使用PagingDataAdapter从它扩展了paging3库RecyclerView.Adapter 。 实现Recyclerview适配器非常相似。 看一看:

class CategoriesAdapter(private val glideDeligate: JlGlideDeligate) :
    PagingDataAdapter<CategoryItem, CategoriesAdapter.CategoriesViewHolder>(diffCallback){




    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CategoriesViewHolder {
        val layoutInflater = LayoutInflater.from(parent.context)
        val v = layoutInflater.inflate(R.layout.lay_list_item, parent, false)
        return CategoriesViewHolder(v,glideDeligate)
    }


    override fun onBindViewHolder(holder: CategoriesViewHolder, position: Int) {
        getItem(position)?.let {
            holder.onBindView(it,position)
        }
    }




    inner class CategoriesViewHolder(
        itemView: View, val glideDeligatee: JlGlideDeligate
    ) : RecyclerView.ViewHolder(itemView) {


        fun onBindView(data: CategoryItem, position: Int) {
            itemView.apply {
                data.icons?.let {
                    if (it.isNotEmpty() &&
                        !it[0].url.isNullOrEmpty())
                        glideDeligatee.load(im_categories,it[0].url!!)
                }


                tv_category?.text = data.name
            }
        }
    }


    companion object {
        
        private val diffCallback = object : DiffUtil.ItemCallback<CategoryItem>() {
            override fun areItemsTheSame(oldItem: CategoryItem, newItem: CategoryItem): Boolean =
                oldItem.id == newItem.id


            override fun areContentsTheSame(oldItem: CategoryItem, newItem: CategoryItem): Boolean =
                oldItem == newItem
        }
    }


}

I’m not going to discuss the adapter as it’s a fundamental implementation of a recyclerview adapter.

我不会讨论适配器,因为它是recyclerview适配器的基本实现。

触发网络更新 (Trigger Network Updates)

Now that we’ve completed the adapter and paging source setup, it’s time to trigger network updates. We need to invoke collectLatest on the flow from the view-model, which returns a receiver. Inside the receiver, we can update the adapter with the latest data set. Have a look:

现在,我们已经完成了适配器和页面源的设置,现在该触发网络更新了。 我们需要在视图模型的流上调用collectLatest ,该模型返回一个接收器。 在接收器内部,我们可以使用最新的数据集更新适配器。 看一看:

class CategoriesActivity : AppCompatActivity() {


    private val categoriesViewModel : CategoriesViewModel by viewModel()
    lateinit var adapter: CategoriesAdapter
    lateinit var glideDeligate: JlGlideDeligate


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)


        glideDeligate = JlGlideDeligate(this)
        adapter = CategoriesAdapter(glideDeligate)
        categories_recyclerView?.adapter = adapter


        categoriesViewModel.scope.launch {
            categoriesViewModel.searchRepo().collectLatest {
                adapter.submitData(it)
            }


        }
    }


}

That’s all the rest of the things will be taken care by the paging library. You can find a working sample in the GitHub repository from here. In the next part of this article, you’re going to learn how to use the map, filter and other functions to implement transformations and add loading, footer, header and retry custom layouts to paging adapter.

分页库将负责所有其他事情。 您可以发现在GitHub的仓库工作示例这里 。 在本文的下一部分中,您将学习如何使用地图,过滤器和其他功能来实现转换,以及将加载,页脚,标头和重试自定义布局添加到分页适配器。

Note: Please create your own Spotify API key from here. Then replace it with the existing key in the project.

注意:请从此处创建您自己的Spotify API密钥。 然后将其替换为项目中的现有密钥。

That is all for now, hope you learned something useful, thanks for reading.

到此为止,希望您能学到一些有用的东西,谢谢阅读。

You can find me on Medium, Twitter, Quora and LinkedIn.

您可以在MediumTwitterQuoraLinkedIn上找到我。

翻译自: https://medium.com/android-dev-hacks/exploring-paging3-part-1-653dc537a69a

1---分页思路

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值