{"id":57258,"date":"2023-05-01T14:03:40","date_gmt":"2023-05-01T08:33:40","guid":{"rendered":"https:\/\/www.tothenew.com\/blog\/?p=57258"},"modified":"2024-01-02T17:41:28","modified_gmt":"2024-01-02T12:11:28","slug":"pagination-with-paging3","status":"publish","type":"post","link":"https:\/\/www.tothenew.com\/blog\/pagination-with-paging3\/","title":{"rendered":"Pagination with Paging3"},"content":{"rendered":"<p><img decoding=\"async\" loading=\"lazy\" class=\"aligncenter \" src=\"\/blog\/wp-ttn-blog\/uploads\/2024\/01\/1pgp1zic4YivNH4X0C1PM7g.webp\" width=\"725\" height=\"339\" \/><br \/>\nDuring the development journey, we may require to show unlimited data as no of users of a particular mobile application grows, so does the data associated with that application.<\/p>\n<p>Consider the image application, the user scrolls to the bottom of the screen and wants to be able to fetch more data from the server to display it, but what if the user scrolls up the screen at the same time? As a result, we will have to deal with a large number of cases and error handling.<\/p>\n<p>We require an approach to do this task that loads all data efficiently from the local database or network, allows caching, reduces resource utilization, and also saves development time.<\/p>\n<p>Paging does this work for you. Paging is a Jetpack library that manages and load data efficiently from different data source; it is compatible with Kotlin and works with threading solutions i.e. Flow, Coroutine, etc. It is designed to follow the Android app architecture. Also, it supports RxJava and LiveData.<\/p>\n<p><strong>Paging3 &amp; Application architecture:<\/strong><\/p>\n<p>Paging3 uses the Android app architecture basic layer like repository-&gt;View Model -&gt; Ui component.<\/p>\n<p><img decoding=\"async\" loading=\"lazy\" class=\"aligncenter size-medium\" src=\"\/blog\/wp-ttn-blog\/uploads\/2024\/01\/1IlYEUmcmqJUG4zYc53PBlg.webp\" width=\"456\" height=\"110\" \/><\/p>\n<p id=\"0eb7\" class=\"pw-post-body-paragraph wl wm vs oh b wn wo wp wq wr ws wt wu nr wv ww wx nw wy wz xa ob xb xc xd xe le bp\" data-selectable-paragraph=\"\"><strong class=\"oh gk\">Paging Source: <\/strong>Paging source is a generic abstract class that takes two type page keys Type and Response Type.<\/p>\n<p id=\"865a\" class=\"pw-post-body-paragraph wl wm vs oh b wn wo wp wq wr ws wt wu nr wv ww wx nw wy wz xa ob xb xc xd xe le bp\" data-selectable-paragraph=\"\"><strong class=\"oh gk\">Pager: <\/strong>This Api consumes a Paging source or remote mediator data source and returns a stream of paged data; it can be flow, Observable, or Live Data.<\/p>\n<p id=\"a74b\" class=\"pw-post-body-paragraph wl wm vs oh b wn wo wp wq wr ws wt wu nr wv ww wx nw wy wz xa ob xb xc xd xe le bp\" data-selectable-paragraph=\"\"><strong class=\"oh gk\">PagingDataAdapter: <\/strong>This is a UI component that is responsible for presenting paged data in the recycler view; it has some additional methods to manage the header and footer at runtime.<\/p>\n<p class=\"pw-post-body-paragraph wl wm vs oh b wn wo wp wq wr ws wt wu nr wv ww wx nw wy wz xa ob xb xc xd xe le bp\" data-selectable-paragraph=\"\"><strong class=\"oh gk\">Paging config:<\/strong> Here, you can define how much data it load for each page.<\/p>\n<h2 id=\"63a9\" class=\"xk xl vs al xm nh xn ni nl nm xo nn nq nr xp ns nv nw xq nx oa ob xr oc of xs bp\" data-selectable-paragraph=\"\"><strong class=\"bc\">Let us code and achieve it<\/strong><\/h2>\n<p id=\"37c4\" class=\"pw-post-body-paragraph wl wm vs oh b wn xt wp wq wr xu wt wu nr xv ww wx nw xw wz xa ob xx xc xd xe le bp\" data-selectable-paragraph=\"\">Add the required dependency in app level gradle:<\/p>\n<blockquote>\n<p data-selectable-paragraph=\"\">implementation <span class=\"hljs-string\">&#8216;androidx.paging:paging-runtime:3.1.1&#8217;<\/span><\/p>\n<\/blockquote>\n<p data-selectable-paragraph=\"\"><span class=\"hljs-string\"><br \/>\nImage Repository:<\/span><\/p>\n<pre><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title.class\">ImageRepository<\/span>(\r\n<span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">val<\/span> apiService: ApiService = RemoteInjector.injectApiService(),\r\n) {\r\n<span class=\"hljs-keyword\">companion<\/span> <span class=\"hljs-keyword\">object<\/span> {\r\n<span class=\"hljs-keyword\">const<\/span> <span class=\"hljs-keyword\">val<\/span> DEFAULT_PAGE_INDEX = <span class=\"hljs-number\">1<\/span>\r\n<span class=\"hljs-keyword\">const<\/span> <span class=\"hljs-keyword\">val<\/span> DEFAULT_PAGE_SIZE = <span class=\"hljs-number\">20<\/span>\r\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">fun<\/span> <span class=\"hljs-title\">getInstance<\/span><span class=\"hljs-params\">()<\/span><\/span> = ImageRepository()\r\n}\r\n\r\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">fun<\/span> <span class=\"hljs-title\">letImagesFlow<\/span><span class=\"hljs-params\">(pagingConfig: <span class=\"hljs-type\">PagingConfig<\/span> = getDefaultPageConfig()<\/span><\/span>): Flow&lt;PagingData&lt;ImageModel&gt;&gt; {\r\n<span class=\"hljs-keyword\">return<\/span> Pager(\r\nconfig = pagingConfig,\r\npagingSourceFactory = { ImagePagingSource(apiService) }\r\n).flow\r\n}\r\n\r\n<span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">fun<\/span> <span class=\"hljs-title\">getDefaultPageConfig<\/span><span class=\"hljs-params\">()<\/span><\/span>: PagingConfig {\r\n<span class=\"hljs-keyword\">return<\/span> PagingConfig(pageSize = DEFAULT_PAGE_SIZE, enablePlaceholders = <span class=\"hljs-literal\">false<\/span>)\r\n}\r\n}<\/pre>\n<p data-selectable-paragraph=\"\"><span class=\"hljs-string\">Image Paging Source:<\/span><\/p>\n<pre><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title.class\">ImagePagingSource<\/span>(<span class=\"hljs-keyword\">var<\/span> apiService: ApiService) : PagingSource&lt;<span class=\"hljs-built_in\">Int<\/span>, ImageModel&gt;() {\r\n\r\n<span class=\"hljs-keyword\">override<\/span> <span class=\"hljs-keyword\">suspend<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">fun<\/span> <span class=\"hljs-title\">load<\/span><span class=\"hljs-params\">(params: <span class=\"hljs-type\">LoadParams<\/span>&lt;<span class=\"hljs-type\">Int<\/span>&gt;)<\/span><\/span>: LoadResult&lt;<span class=\"hljs-built_in\">Int<\/span>, ImageModel&gt; {\r\n<span class=\"hljs-keyword\">val<\/span> page = params.key ?: DEFAULT_PAGE_INDEX\r\n<span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-keyword\">try<\/span> {\r\n<span class=\"hljs-keyword\">val<\/span> response = apiService.getImages(page, params.loadSize)\r\n\r\nLoadResult.Page(\r\nresponse, prevKey = <span class=\"hljs-keyword\">if<\/span> (page == DEFAULT_PAGE_INDEX) <span class=\"hljs-literal\">null<\/span> <span class=\"hljs-keyword\">else<\/span> page - <span class=\"hljs-number\">1<\/span>,\r\nnextKey = <span class=\"hljs-keyword\">if<\/span> (response.isEmpty()) <span class=\"hljs-literal\">null<\/span> <span class=\"hljs-keyword\">else<\/span> page + <span class=\"hljs-number\">1<\/span>\r\n)\r\n\r\n} <span class=\"hljs-keyword\">catch<\/span> (exception: IOException) {\r\n<span class=\"hljs-keyword\">return<\/span> LoadResult.Error(exception)\r\n} <span class=\"hljs-keyword\">catch<\/span> (exception: HttpException) {\r\n<span class=\"hljs-keyword\">return<\/span> LoadResult.Error(exception)\r\n}\r\n}\r\n\r\n<span class=\"hljs-keyword\">override<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">fun<\/span> <span class=\"hljs-title\">getRefreshKey<\/span><span class=\"hljs-params\">(state: <span class=\"hljs-type\">PagingState<\/span>&lt;<span class=\"hljs-type\">Int<\/span>, ImageModel&gt;)<\/span><\/span>: <span class=\"hljs-built_in\">Int<\/span>? {\r\n\r\n}\r\n}<\/pre>\n<p data-selectable-paragraph=\"\">Images View Model:<\/p>\n<pre><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title.class\">ImagesViewModel<\/span>(<span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">val<\/span> repository: ImageRepository = ImageRepository.getInstance()) :\r\nViewModel() {\r\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">fun<\/span> <span class=\"hljs-title\">fetchImages<\/span><span class=\"hljs-params\">()<\/span><\/span>: Flow&lt;PagingData&lt;String&gt;&gt; {\r\n<span class=\"hljs-keyword\">return<\/span> repository.letImagesFlow()\r\n.map { it -&gt; it.map { it.url } }\r\n.cachedIn(viewModelScope)\r\n}\r\n}\r\n\r\nImages Adapter:\r\n<span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title.class\">ImagesAdapter<\/span> :\r\n<span class=\"hljs-type\">PagingDataAdapter<\/span>&lt;<span class=\"hljs-type\">String, UserViewHolder<\/span>&gt;(<span class=\"hljs-keyword\">object<\/span> : DiffUtil.ItemCallback&lt;String&gt;() {\r\n<span class=\"hljs-keyword\">override<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">fun<\/span> <span class=\"hljs-title\">areItemsTheSame<\/span><span class=\"hljs-params\">(oldItem: <span class=\"hljs-type\">String<\/span>, newItem: <span class=\"hljs-type\">String<\/span>)<\/span><\/span>: <span class=\"hljs-built_in\">Boolean<\/span> {\r\n<span class=\"hljs-keyword\">return<\/span> oldItem == newItem\r\n}\r\n\r\n<span class=\"hljs-keyword\">override<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">fun<\/span> <span class=\"hljs-title\">areContentsTheSame<\/span><span class=\"hljs-params\">(oldItem: <span class=\"hljs-type\">String<\/span>, newItem: <span class=\"hljs-type\">String<\/span>)<\/span><\/span>: <span class=\"hljs-built_in\">Boolean<\/span> {\r\n<span class=\"hljs-keyword\">return<\/span> oldItem == newItem\r\n}\r\n}) {\r\n<span class=\"hljs-keyword\">override<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">fun<\/span> <span class=\"hljs-title\">onCreateViewHolder<\/span><span class=\"hljs-params\">(\r\nparent: <span class=\"hljs-type\">ViewGroup<\/span>,\r\nviewType: <span class=\"hljs-type\">Int<\/span>\r\n)<\/span><\/span>: UserViewHolder {\r\n<span class=\"hljs-keyword\">val<\/span> inflater = LayoutInflater.from(parent.context)\r\n\r\n<span class=\"hljs-keyword\">return<\/span> UserViewHolder(inflater.inflate(R.layout.item_image_view, parent,<span class=\"hljs-literal\">false<\/span>))\r\n}\r\n\r\n<span class=\"hljs-keyword\">override<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">fun<\/span> <span class=\"hljs-title\">onBindViewHolder<\/span><span class=\"hljs-params\">(holder: <span class=\"hljs-type\">UserViewHolder<\/span>, position: <span class=\"hljs-type\">Int<\/span>)<\/span><\/span> {\r\n<span class=\"hljs-keyword\">val<\/span> item = getItem(position)\r\nholder.bind(item)\r\n\r\n}\r\n}\r\n\r\n<span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title.class\">UserViewHolder<\/span>(itemView: View) : RecyclerView.ViewHolder(itemView) {\r\n<span class=\"hljs-function\"><span class=\"hljs-keyword\">fun<\/span> <span class=\"hljs-title\">bind<\/span><span class=\"hljs-params\">(url: <span class=\"hljs-type\">String<\/span>?)<\/span><\/span> {\r\n<span class=\"hljs-keyword\">val<\/span> image = itemView.findViewById&lt;ImageView&gt;(R.id.ivMain)\r\nGlide.with(itemView.context).load(url).into(image)\r\n}\r\n}<\/pre>\n<p data-selectable-paragraph=\"\">Images Fragment:<\/p>\n<pre><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title.class\">ImagesFragment<\/span> : <span class=\"hljs-type\">Fragment<\/span>() {\r\n\r\n<span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">lateinit<\/span> <span class=\"hljs-keyword\">var<\/span> viewModel: ImagesViewModel\r\n<span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">var<\/span> _binding: FragmentImagesBinding? = <span class=\"hljs-literal\">null<\/span>\r\n<span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">lateinit<\/span> <span class=\"hljs-keyword\">var<\/span> adapter: ImagesAdapter\r\n<span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">lateinit<\/span> <span class=\"hljs-keyword\">var<\/span> loaderAdapter: LoaderStateAdapter\r\n\r\n<span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-keyword\">val<\/span> binding <span class=\"hljs-keyword\">get<\/span>() = _binding!!\r\n\r\n<span class=\"hljs-keyword\">override<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">fun<\/span> <span class=\"hljs-title\">onCreateView<\/span><span class=\"hljs-params\">(\r\ninflater: <span class=\"hljs-type\">LayoutInflater<\/span>, container: <span class=\"hljs-type\">ViewGroup<\/span>?,\r\nsavedInstanceState: <span class=\"hljs-type\">Bundle<\/span>?\r\n)<\/span><\/span>: View {\r\n_binding = FragmentImagesBinding.inflate(inflater, container, <span class=\"hljs-literal\">false<\/span>)\r\n<span class=\"hljs-keyword\">return<\/span> binding.root\r\n\r\n}\r\n\r\n<span class=\"hljs-keyword\">override<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">fun<\/span> <span class=\"hljs-title\">onViewCreated<\/span><span class=\"hljs-params\">(view: <span class=\"hljs-type\">View<\/span>, savedInstanceState: <span class=\"hljs-type\">Bundle<\/span>?)<\/span><\/span> {\r\n<span class=\"hljs-keyword\">super<\/span>.onViewCreated(view, savedInstanceState)\r\n<span class=\"hljs-keyword\">init<\/span>()\r\nsetupUi()\r\nlifecycleScope.launch {\r\nviewModel.fetchImages().distinctUntilChanged().collectLatest {\r\nadapter.submitData(it)\r\n}\r\n}\r\n}\r\n\r\n<span class=\"hljs-keyword\">override<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">fun<\/span> <span class=\"hljs-title\">onDestroyView<\/span><span class=\"hljs-params\">()<\/span><\/span> {\r\n<span class=\"hljs-keyword\">super<\/span>.onDestroyView()\r\n_binding = <span class=\"hljs-literal\">null<\/span>\r\n}\r\n\r\n<span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">fun<\/span> <span class=\"hljs-title\">init<\/span><span class=\"hljs-params\">()<\/span><\/span> {\r\nviewModel = defaultViewModelProviderFactory.create(ImagesViewModel::<span class=\"hljs-keyword\">class<\/span>.java)\r\nadapter = ImagesAdapter()\r\nloaderAdapter = LoaderStateAdapter()\r\n}\r\n\r\n<span class=\"hljs-keyword\">private<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">fun<\/span> <span class=\"hljs-title\">setupUi<\/span><span class=\"hljs-params\">()<\/span><\/span> {\r\n<span class=\"hljs-keyword\">val<\/span> manager = GridLayoutManager(context, <span class=\"hljs-number\">2<\/span>)\r\n\r\nbinding.rvImages.layoutManager = manager\r\nbinding.rvImages.adapter = adapter.withLoadStateFooter(loaderAdapter)\r\n\r\n}\r\n}<\/pre>\n<p data-selectable-paragraph=\"\">If you run the above code, it will produce output like below:<\/p>\n<p data-selectable-paragraph=\"\"><img decoding=\"async\" loading=\"lazy\" class=\"aligncenter \" src=\"\/blog\/wp-ttn-blog\/uploads\/2024\/01\/1DcPckg7b6zZAoUkq2TKiug.gif\" width=\"481\" height=\"962\" \/><\/p>\n<p id=\"696f\" class=\"wl wm yn oh b wn wo wp wq wr ws wt wu yo wv ww wx yp wy wz xa yq xb xc xd xe le bp\" data-selectable-paragraph=\"\">If you are facing some use cases, feel free to comment and clap if you enjoyed.<\/p>\n<p id=\"e043\" class=\"wl wm yn oh b wn wo wp wq wr ws wt wu yo wv ww wx yp wy wz xa yq xb xc xd xe le bp\" data-selectable-paragraph=\"\">Find the complete code on <a class=\"ay hp\" href=\"https:\/\/github.com\/reyanshttn\/pagination\" target=\"_blank\" rel=\"noopener ugc nofollow\">Github<\/a>.<\/p>\n<p id=\"8989\" class=\"wl wm yn oh b wn wo wp wq wr ws wt wu yo wv ww wx yp wy wz xa yq xb xc xd xe le bp\" data-selectable-paragraph=\"\">Thank you for reading, and Happy Coding!<br \/>\n<strong class=\"oh gk\"><br \/>\nRefrences:<br \/>\n<\/strong><a class=\"ay hp\" href=\"https:\/\/developer.android.com\/topic\/libraries\/architecture\/paging\/v3-paged-data\" target=\"_blank\" rel=\"noopener ugc nofollow\">https:\/\/developer.android.com\/topic\/libraries\/architecture\/paging\/v3-paged-data<\/a><\/p>\n<div class=\"ap-custom-wrapper\"><\/div><!--ap-custom-wrapper-->","protected":false},"excerpt":{"rendered":"<p>During the development journey, we may require to show unlimited data as no of users of a particular mobile application grows, so does the data associated with that application. Consider the image application, the user scrolls to the bottom of the screen and wants to be able to fetch more data from the server to [&hellip;]<\/p>\n","protected":false},"author":1560,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"iawp_total_views":9},"categories":[518],"tags":[4845,5217,83,5218],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts\/57258"}],"collection":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/users\/1560"}],"replies":[{"embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/comments?post=57258"}],"version-history":[{"count":3,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts\/57258\/revisions"}],"predecessor-version":[{"id":59701,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts\/57258\/revisions\/59701"}],"wp:attachment":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/media?parent=57258"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/categories?post=57258"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/tags?post=57258"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}