{"data":{"allMarkdownRemark":{"edges":[{"node":{"id":"9720ef25-7d81-5307-9ea5-229d55ec766f","frontmatter":{"category":"Engineering","title":"Android JetPack Compose + Paging 3","date":"2024-03-29","summary":"A short trip around the AUTO1 Application Cockpit data model","thumbnail":null,"authorName":"Sergey Bakhtiarov","authorDescription":"Senior Android Developer at AUTO1 Group","authorAvatar":{"relativePath":"pages/android-compose-pagination/avatar_Sergey.jpeg","childImageSharp":{"resolutions":{"base64":"data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAbABQDASIAAhEBAxEB/8QAGAAAAwEBAAAAAAAAAAAAAAAAAAIEBgX/xAAWAQEBAQAAAAAAAAAAAAAAAAABAgD/2gAMAwEAAhADEAAAAessVEW4wzlnkZ2tJg3/xAAdEAACAQQDAAAAAAAAAAAAAAABAgMABCExECIz/9oACAEBAAEFAsAAq1EVIe0OSdtKzEXEgbfMHh//xAAXEQADAQAAAAAAAAAAAAAAAAAQERIh/9oACAEDAQE/AZxhj//EABURAQEAAAAAAAAAAAAAAAAAABEg/9oACAECAQE/AWP/xAAdEAACAgEFAAAAAAAAAAAAAAABEQAQAiAhImFx/9oACAEBAAY/AtzOJt9U3GCtGHk//8QAGxABAAIDAQEAAAAAAAAAAAAAAQARECExQVH/2gAIAQEAAT8hd4gIS0NTZPIjUV1DC40rgQLqvDjL0fpeUqrP/9oADAMBAAIAAwAAABD/APKz/8QAFREBAQAAAAAAAAAAAAAAAAAAEBH/2gAIAQMBAT8QBR//xAAXEQEBAQEAAAAAAAAAAAAAAAABEBEh/9oACAECAQE/EOnIBP/EAB0QAQEBAAMAAwEAAAAAAAAAAAERACExQVFhcdH/2gAIAQEAAT8QaCJVXrPnop5ivjMtoBBeM7oBFG2x3Zjtir0D+518AlC+E9yQMgcPsxpw/mVwqOXf/9k=","width":50,"height":50,"src":"/static/27c2b15cdd840519c853a653e9b049f0/d2d31/avatar_Sergey.jpeg","srcSet":"/static/27c2b15cdd840519c853a653e9b049f0/d2d31/avatar_Sergey.jpeg 1x,\n/static/27c2b15cdd840519c853a653e9b049f0/0b804/avatar_Sergey.jpeg 1.5x,\n/static/27c2b15cdd840519c853a653e9b049f0/753c3/avatar_Sergey.jpeg 2x,\n/static/27c2b15cdd840519c853a653e9b049f0/31ca8/avatar_Sergey.jpeg 3x"}}},"headerImage":null},"html":"<h1>Android JetPack Compose + Paging 3</h1>\n<h2>About pagination</h2>\n<p>Paginated APIs, often encountered when dealing with servers and databases, break down large datasets into manageable chunks or pages. This methodology not only optimizes resource usage but also enhances the overall user experience by delivering content progressively. However, integrating and managing paginated data in a mobile app can be a challenge, from maintaining loading states to ensuring a seamless transition between pages and supporting content filtering.</p>\n<p>In this guide I will show you how to work with paginated APIs in Android with Jetpack Compose and Paging 3 libraries. </p>\n<p><img src=\"/static/paging_demo-a325402259bf9864dced311c9eafa7c8.gif\" alt=\"Paging UI\"></p>\n<h2>Basics, architecture overview</h2>\n<p><br></br>\n<img src=\"https://developer.android.com/static/topic/libraries/architecture/images/paging3-library-architecture.svg\" alt=\"Paging library overview\"></p>\n<p>The key components of the Paging 3 architecture include</p>\n<h3>PagingSource</h3>\n<ul>\n<li>The foundational element responsible for loading data in chunks.</li>\n<li>Developers implement a custom PagingSource, defining how to retrieve data from a particular source or use implementation provided by a library supporting Paging 3. (Room can generate a paging source for your data query)</li>\n</ul>\n<h3>Remote Mediator</h3>\n<ul>\n<li>An integral part of the Paging 3 architecture, RemoteMediator manages the coordination between remote data sources, typically backed by a network service, and the local database.</li>\n<li>Responsible for loading pages of data from the network and storing them in the local database, ensuring efficient and reliable pagination.</li>\n</ul>\n<h3>Paging Data</h3>\n<ul>\n<li>Represents the paginated data stream emitted by the PagingSource.</li>\n<li>A Flow of PagingData is observed in the UI layer, enabling dynamic updates as new data is loaded or existing data is invalidated.</li>\n</ul>\n<h3>Pager</h3>\n<ul>\n<li>Coordinates the interaction between the PagingSource and the UI layer.</li>\n<li>Configures the pagination parameters, such as page size, prefetch distance, and initial load size, providing fine-grained control over the loading behavior.</li>\n</ul>\n<h2>Data layer</h2>\n<p>We start with the data layer and define a Retrofit API for fetching movies from network service.</p>\n<div class=\"gatsby-highlight\" data-language=\"kotlin\"><pre class=\"language-kotlin\"><code class=\"language-kotlin\"><span class=\"token keyword\">interface</span> MoviesApi <span class=\"token punctuation\">{</span>\n  <span class=\"token annotation builtin\">@GET</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"/3/discover/movie?language=en-US&amp;sort_by=popularity.desc\"</span><span class=\"token punctuation\">)</span>\n  <span class=\"token keyword\">suspend</span> <span class=\"token keyword\">fun</span> <span class=\"token function\">discover</span><span class=\"token punctuation\">(</span>\n    <span class=\"token annotation builtin\">@Query</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"api_key\"</span><span class=\"token punctuation\">)</span> api_key<span class=\"token operator\">:</span> String<span class=\"token punctuation\">,</span>\n    <span class=\"token annotation builtin\">@Query</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"page\"</span><span class=\"token punctuation\">)</span> page<span class=\"token operator\">:</span> Int<span class=\"token punctuation\">,</span>\n    <span class=\"token annotation builtin\">@Query</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"with_genres\"</span><span class=\"token punctuation\">)</span> genres<span class=\"token operator\">:</span> String<span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> Response<span class=\"token operator\">&lt;</span>MoviesNetworkResponse<span class=\"token operator\">></span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>Next, we will leverage the Room library, which is compatible with Paging 3, to create a local paging source. </p>\n<div class=\"gatsby-highlight\" data-language=\"kotlin\"><pre class=\"language-kotlin\"><code class=\"language-kotlin\"><span class=\"token annotation builtin\">@Dao</span>\n<span class=\"token keyword\">interface</span> MoviesDao <span class=\"token punctuation\">{</span>\n  <span class=\"token annotation builtin\">@Query</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"SELECT * FROM movies ORDER BY id ASC\"</span><span class=\"token punctuation\">)</span>\n  <span class=\"token keyword\">fun</span> <span class=\"token function\">getMovies</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> DataSource<span class=\"token punctuation\">.</span>Factory<span class=\"token operator\">&lt;</span>Int<span class=\"token punctuation\">,</span> MovieDbEntity<span class=\"token operator\">></span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>It's important to highlight that we utilize <code class=\"language-text\">DataSource.Factory</code> as the return type instead of <code class=\"language-text\">PagingSource</code>. This choice is made specifically because <code class=\"language-text\">DataSource.Factory</code> offers the <code class=\"language-text\">map()</code> method, enabling us to map <code class=\"language-text\">MovieDbEntity</code> instances into domain layer models.</p>\n<p>Now we need to put together local and remote data sources in Remote mediator.</p>\n<ol>\n<li>Determine which page to load depending on the loadType and next page value. Next page number (<code class=\"language-text\">getRemoteKey().nextPage</code>) is stored in the database after each successful network request.</li>\n</ol>\n<div class=\"gatsby-highlight\" data-language=\"kotlin\"><pre class=\"language-kotlin\"><code class=\"language-kotlin\"><span class=\"token keyword\">val</span> page <span class=\"token operator\">=</span> <span class=\"token keyword\">when</span> <span class=\"token punctuation\">(</span>loadType<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        LoadType<span class=\"token punctuation\">.</span>REFRESH <span class=\"token operator\">-></span> <span class=\"token number\">1</span>\n        LoadType<span class=\"token punctuation\">.</span>PREPEND <span class=\"token operator\">-></span> <span class=\"token keyword\">null</span>\n        LoadType<span class=\"token punctuation\">.</span>APPEND <span class=\"token operator\">-></span> repository<span class=\"token punctuation\">.</span><span class=\"token function\">getRemoteKey</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token operator\">?</span><span class=\"token punctuation\">.</span>nextPage\n      <span class=\"token punctuation\">}</span> <span class=\"token operator\">?:</span> <span class=\"token keyword\">return</span> <span class=\"token function\">Success</span><span class=\"token punctuation\">(</span>endOfPaginationReached <span class=\"token operator\">=</span> <span class=\"token boolean\">true</span><span class=\"token punctuation\">)</span></code></pre></div>\n<p>For bidirectional pagination support, provide the preceding page number for the PREPEND load type. The REFRESH load type is employed when content is refreshed, initiating loading from the initial page.</p>\n<ol start=\"2\">\n<li>Request data from network</li>\n</ol>\n<p>Call our network data source method to fetch a page of data from the server.</p>\n<div class=\"gatsby-highlight\" data-language=\"kotlin\"><pre class=\"language-kotlin\"><code class=\"language-kotlin\"><span class=\"token keyword\">val</span> movies <span class=\"token operator\">=</span> moviesDataSource<span class=\"token punctuation\">.</span><span class=\"token function\">discover</span><span class=\"token punctuation\">(</span>page<span class=\"token punctuation\">)</span></code></pre></div>\n<ol start=\"3\">\n<li>Insert data into the database or refresh database if loadType is refresh</li>\n</ol>\n<p>The RemoteMediator code will appear as follows:</p>\n<div class=\"gatsby-highlight\" data-language=\"kotlin\"><pre class=\"language-kotlin\"><code class=\"language-kotlin\"><span class=\"token keyword\">class</span> <span class=\"token function\">MoviesRemoteMediator</span><span class=\"token punctuation\">(</span>\n  <span class=\"token keyword\">private</span> <span class=\"token keyword\">val</span> repository<span class=\"token operator\">:</span> MoviesRepository<span class=\"token punctuation\">,</span>\n  <span class=\"token keyword\">private</span> <span class=\"token keyword\">val</span> moviesDataSource<span class=\"token operator\">:</span> NetworkMoviesDataSource<span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">)</span> <span class=\"token operator\">:</span> RemoteMediator<span class=\"token operator\">&lt;</span>Int<span class=\"token punctuation\">,</span> Movie<span class=\"token operator\">></span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n\n  <span class=\"token keyword\">override</span> <span class=\"token keyword\">suspend</span> <span class=\"token keyword\">fun</span> <span class=\"token function\">load</span><span class=\"token punctuation\">(</span>loadType<span class=\"token operator\">:</span> LoadType<span class=\"token punctuation\">,</span> state<span class=\"token operator\">:</span> PagingState<span class=\"token operator\">&lt;</span>Int<span class=\"token punctuation\">,</span> Movie<span class=\"token operator\">></span><span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> MediatorResult <span class=\"token punctuation\">{</span>\n\n    <span class=\"token keyword\">return</span> <span class=\"token keyword\">try</span> <span class=\"token punctuation\">{</span>\n\n      <span class=\"token keyword\">val</span> page <span class=\"token operator\">=</span> <span class=\"token keyword\">when</span> <span class=\"token punctuation\">(</span>loadType<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        LoadType<span class=\"token punctuation\">.</span>REFRESH <span class=\"token operator\">-></span> <span class=\"token number\">1</span>\n        LoadType<span class=\"token punctuation\">.</span>PREPEND <span class=\"token operator\">-></span> <span class=\"token keyword\">null</span>\n        LoadType<span class=\"token punctuation\">.</span>APPEND <span class=\"token operator\">-></span> repository<span class=\"token punctuation\">.</span><span class=\"token function\">getRemoteKey</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token operator\">?</span><span class=\"token punctuation\">.</span>nextPage\n      <span class=\"token punctuation\">}</span> <span class=\"token operator\">?:</span> <span class=\"token keyword\">return</span> <span class=\"token function\">Success</span><span class=\"token punctuation\">(</span>endOfPaginationReached <span class=\"token operator\">=</span> <span class=\"token boolean\">true</span><span class=\"token punctuation\">)</span>\n\n      <span class=\"token keyword\">val</span> movies <span class=\"token operator\">=</span> moviesDataSource<span class=\"token punctuation\">.</span><span class=\"token function\">getMovies</span><span class=\"token punctuation\">(</span>page<span class=\"token punctuation\">)</span>\n\n      <span class=\"token keyword\">val</span> nextPage <span class=\"token operator\">=</span> <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>movies<span class=\"token punctuation\">.</span><span class=\"token function\">isNotEmpty</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span> page <span class=\"token operator\">+</span> <span class=\"token number\">1</span> <span class=\"token keyword\">else</span> <span class=\"token keyword\">null</span>\n\n      repository<span class=\"token punctuation\">.</span><span class=\"token function\">insertMovies</span><span class=\"token punctuation\">(</span>movies<span class=\"token punctuation\">,</span> nextPage<span class=\"token punctuation\">,</span> loadType <span class=\"token operator\">==</span> LoadType<span class=\"token punctuation\">.</span>REFRESH<span class=\"token punctuation\">)</span>\n\n      <span class=\"token function\">Success</span><span class=\"token punctuation\">(</span>endOfPaginationReached <span class=\"token operator\">=</span> movies<span class=\"token punctuation\">.</span><span class=\"token function\">isEmpty</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n\n    <span class=\"token punctuation\">}</span> <span class=\"token keyword\">catch</span> <span class=\"token punctuation\">(</span>e<span class=\"token operator\">:</span> Exception<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n      MediatorResult<span class=\"token punctuation\">.</span><span class=\"token function\">Error</span><span class=\"token punctuation\">(</span>e<span class=\"token punctuation\">)</span>\n    <span class=\"token punctuation\">}</span>\n  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>RemoteMediator uses the repository to store movies list and next page number in the database.\nRepository also takes care of clearing the database table when data is refreshed:</p>\n<div class=\"gatsby-highlight\" data-language=\"kotlin\"><pre class=\"language-kotlin\"><code class=\"language-kotlin\"><span class=\"token keyword\">class</span> <span class=\"token function\">MoviesRepositoryImpl</span><span class=\"token punctuation\">(</span>\n  <span class=\"token keyword\">private</span> <span class=\"token keyword\">val</span> database<span class=\"token operator\">:</span> MoviesDatabase\n<span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> MoviesRepository <span class=\"token punctuation\">{</span>\n\n  <span class=\"token keyword\">override</span> <span class=\"token keyword\">fun</span> <span class=\"token function\">getMovies</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=</span> database<span class=\"token punctuation\">.</span><span class=\"token function\">movies</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">getMovies</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">map</span> <span class=\"token punctuation\">{</span> it<span class=\"token punctuation\">.</span><span class=\"token function\">toDomain</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">}</span>\n\n  <span class=\"token keyword\">override</span> <span class=\"token keyword\">suspend</span> <span class=\"token keyword\">fun</span> <span class=\"token function\">insertMovies</span><span class=\"token punctuation\">(</span>movies<span class=\"token operator\">:</span> List<span class=\"token operator\">&lt;</span>Movie<span class=\"token operator\">></span><span class=\"token punctuation\">,</span> nextPage<span class=\"token operator\">:</span> Int<span class=\"token operator\">?</span><span class=\"token punctuation\">,</span> clear<span class=\"token operator\">:</span> Boolean<span class=\"token punctuation\">)</span><span class=\"token punctuation\">{</span>\n\n    database<span class=\"token punctuation\">.</span><span class=\"token function\">withTransaction</span> <span class=\"token punctuation\">{</span>\n      <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>clear<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        database<span class=\"token punctuation\">.</span><span class=\"token function\">movies</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">clear</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n      <span class=\"token punctuation\">}</span>\n\n      database<span class=\"token punctuation\">.</span><span class=\"token function\">movies</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">putMovies</span><span class=\"token punctuation\">(</span>movies<span class=\"token punctuation\">.</span><span class=\"token function\">map</span> <span class=\"token punctuation\">{</span> it<span class=\"token punctuation\">.</span><span class=\"token function\">toDbEntity</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n\n      <span class=\"token keyword\">if</span> <span class=\"token punctuation\">(</span>nextPage <span class=\"token operator\">!=</span> <span class=\"token keyword\">null</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n        database<span class=\"token punctuation\">.</span><span class=\"token function\">remoteKey</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">insertKey</span><span class=\"token punctuation\">(</span><span class=\"token function\">RemoteKey</span><span class=\"token punctuation\">(</span>MoviesDatabase<span class=\"token punctuation\">.</span>MOVIES_REMOTE_KEY<span class=\"token punctuation\">,</span> nextPage<span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n      <span class=\"token punctuation\">}</span>\n    <span class=\"token punctuation\">}</span>\n  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<h2>UI layer</h2>\n<p>The Pager object serves as a bridge between remote and local data sources. It is managing the page loading, prefetching, and handling the transitions between various data sources to provide a seamless and responsive flow of paginated content within your application. You can configure the page loading behavior with the following parameters:</p>\n<ul>\n<li><strong>pageSize</strong> parameter defines the number of items loaded in each page of data.</li>\n<li><strong>initialLoadSize</strong> is the number of items loaded initially when the paging library is first initialized.</li>\n<li><strong>prefetchDistance</strong> parameter determines the distance from the edge of the loaded content at which the next page should start loading.</li>\n</ul>\n<div class=\"gatsby-highlight\" data-language=\"kotlin\"><pre class=\"language-kotlin\"><code class=\"language-kotlin\"><span class=\"token keyword\">class</span> <span class=\"token function\">MoviesScreenViewModel</span><span class=\"token punctuation\">(</span>\n  moviesSource<span class=\"token operator\">:</span> MoviesRemoteMediator<span class=\"token punctuation\">,</span>\n  repository<span class=\"token operator\">:</span> MoviesRepository<span class=\"token punctuation\">,</span>\n  <span class=\"token keyword\">private</span> <span class=\"token keyword\">val</span> movieDetails<span class=\"token operator\">:</span> MovieDetailsApi<span class=\"token punctuation\">,</span>\n<span class=\"token punctuation\">)</span> <span class=\"token operator\">:</span> <span class=\"token function\">ViewModel</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n   <span class=\"token keyword\">val</span> moviesFlow <span class=\"token operator\">=</span> <span class=\"token function\">Pager</span><span class=\"token punctuation\">(</span>\n      config <span class=\"token operator\">=</span> <span class=\"token function\">PagingConfig</span><span class=\"token punctuation\">(</span>\n        pageSize <span class=\"token operator\">=</span> PAGE_SIZE<span class=\"token punctuation\">,</span>\n        prefetchDistance <span class=\"token operator\">=</span> PREFETCH_DISTANCE<span class=\"token punctuation\">,</span>\n        initialLoadSize <span class=\"token operator\">=</span> INITIAL_LOAD<span class=\"token punctuation\">,</span>\n      <span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n      remoteMediator <span class=\"token operator\">=</span> moviesSource<span class=\"token punctuation\">,</span>\n      pagingSourceFactory <span class=\"token operator\">=</span> repository<span class=\"token punctuation\">.</span><span class=\"token function\">getMovies</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">asPagingSourceFactory</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n    <span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span>flow<span class=\"token punctuation\">.</span><span class=\"token function\">cachedIn</span><span class=\"token punctuation\">(</span>viewModelScope<span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<h3>Consuming paging data in Jetpack Compose UI</h3>\n<p>Now, let's explore how to consume the paginated data in your Compose UI.</p>\n<p>Use the <code class=\"language-text\">collectAsLazyPagingItems</code> extension function to collect the <code class=\"language-text\">PagingData</code> and observe changes.</p>\n<div class=\"gatsby-highlight\" data-language=\"kotlin\"><pre class=\"language-kotlin\"><code class=\"language-kotlin\"><span class=\"token keyword\">val</span> movies <span class=\"token operator\">=</span> viewModel<span class=\"token punctuation\">.</span>moviesFlow<span class=\"token punctuation\">.</span><span class=\"token function\">collectAsLazyPagingItems</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span></code></pre></div>\n<p>Use the <code class=\"language-text\">LazyColumn</code> or <code class=\"language-text\">LazyVerticalGrid</code> to efficiently display paginated data in your Compose UI.</p>\n<div class=\"gatsby-highlight\" data-language=\"kotlin\"><pre class=\"language-kotlin\"><code class=\"language-kotlin\"><span class=\"token function\">LazyVerticalGrid</span><span class=\"token punctuation\">(</span>\n      modifier <span class=\"token operator\">=</span> Modifier\n        <span class=\"token punctuation\">.</span><span class=\"token function\">fillMaxSize</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">.</span><span class=\"token function\">padding</span><span class=\"token punctuation\">(</span><span class=\"token number\">16</span><span class=\"token punctuation\">.</span>dp<span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n      columns <span class=\"token operator\">=</span> GridCells<span class=\"token punctuation\">.</span><span class=\"token function\">Fixed</span><span class=\"token punctuation\">(</span><span class=\"token number\">3</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n      horizontalArrangement <span class=\"token operator\">=</span> Arrangement<span class=\"token punctuation\">.</span><span class=\"token function\">spacedBy</span><span class=\"token punctuation\">(</span><span class=\"token number\">16</span><span class=\"token punctuation\">.</span>dp<span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n      verticalArrangement <span class=\"token operator\">=</span> Arrangement<span class=\"token punctuation\">.</span><span class=\"token function\">spacedBy</span><span class=\"token punctuation\">(</span><span class=\"token number\">16</span><span class=\"token punctuation\">.</span>dp<span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n    <span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n\n      <span class=\"token function\">items</span><span class=\"token punctuation\">(</span>\n        count <span class=\"token operator\">=</span> movies<span class=\"token punctuation\">.</span>itemCount<span class=\"token punctuation\">,</span>\n        key <span class=\"token operator\">=</span> movies<span class=\"token punctuation\">.</span><span class=\"token function\">itemKey</span> <span class=\"token punctuation\">{</span> it<span class=\"token punctuation\">.</span>id <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n      <span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span> index <span class=\"token operator\">-></span>\n\n        movies<span class=\"token punctuation\">[</span>index<span class=\"token punctuation\">]</span><span class=\"token operator\">?</span><span class=\"token punctuation\">.</span><span class=\"token function\">let</span> <span class=\"token punctuation\">{</span> movie <span class=\"token operator\">-></span>\n          <span class=\"token function\">MovieCardSmall</span><span class=\"token punctuation\">(</span>\n            movie <span class=\"token operator\">=</span> movie<span class=\"token punctuation\">,</span>\n            onClick <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span> <span class=\"token function\">onClick</span><span class=\"token punctuation\">(</span>movie<span class=\"token punctuation\">.</span>id<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span>\n          <span class=\"token punctuation\">)</span>\n        <span class=\"token punctuation\">}</span>\n      <span class=\"token punctuation\">}</span>\n    <span class=\"token punctuation\">}</span></code></pre></div>\n<hr>\n<blockquote>\n<p><em>Note the usage of the key property for items. The key property uniquely identifies each item, allowing Compose to efficiently update and recycle composables as data changes.</em></p>\n</blockquote>\n<hr>\n<h3>Paging state handling</h3>\n<p>LazyPagingItems offers insights into the ongoing data loading process, allowing you to effectively manage errors during pagination. By examining the loadState.append property, you can determine the loading state when appending new items and present a user-friendly indication of the current status.</p>\n<p>For instance, the following code snippet demonstrates how to react to different loading states during item appending:</p>\n<div class=\"gatsby-highlight\" data-language=\"kotlin\"><pre class=\"language-kotlin\"><code class=\"language-kotlin\"><span class=\"token keyword\">when</span> <span class=\"token punctuation\">(</span>movies<span class=\"token punctuation\">.</span>loadState<span class=\"token punctuation\">.</span>append<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token keyword\">is</span> LoadState<span class=\"token punctuation\">.</span>Loading <span class=\"token operator\">-></span> <span class=\"token punctuation\">{</span> <span class=\"token function\">ProgressFooter</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">}</span>\n  <span class=\"token keyword\">is</span> LoadState<span class=\"token punctuation\">.</span>Error <span class=\"token operator\">-></span> <span class=\"token punctuation\">{</span> <span class=\"token function\">ErrorFooter</span><span class=\"token punctuation\">(</span>onRetryClick <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span> movies<span class=\"token punctuation\">.</span><span class=\"token function\">retry</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">}</span> <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>Similarly, you can assess the loading status when the list data is refreshed by checking the loadState.refresh property. Extension functions isError() and isLoading() provide convenient checks for error and loading states, respectively.</p>\n<div class=\"gatsby-highlight\" data-language=\"kotlin\"><pre class=\"language-kotlin\"><code class=\"language-kotlin\"><span class=\"token keyword\">fun</span> LazyPagingItems<span class=\"token operator\">&lt;</span>Movie<span class=\"token operator\">></span><span class=\"token punctuation\">.</span><span class=\"token function\">isError</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> Boolean <span class=\"token operator\">=</span> \n    loadState<span class=\"token punctuation\">.</span>refresh <span class=\"token keyword\">is</span> LoadState<span class=\"token punctuation\">.</span>Error <span class=\"token operator\">&amp;&amp;</span> itemSnapshotList<span class=\"token punctuation\">.</span><span class=\"token function\">isEmpty</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n\n<span class=\"token keyword\">fun</span> LazyPagingItems<span class=\"token operator\">&lt;</span>Movie<span class=\"token operator\">></span><span class=\"token punctuation\">.</span><span class=\"token function\">isLoading</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> Boolean <span class=\"token operator\">=</span> \n    loadState<span class=\"token punctuation\">.</span>refresh <span class=\"token keyword\">is</span> LoadState<span class=\"token punctuation\">.</span>Loading <span class=\"token operator\">&amp;&amp;</span> itemSnapshotList<span class=\"token punctuation\">.</span><span class=\"token function\">isEmpty</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span></code></pre></div>\n<p>Incorporate these checks into your UI logic to display appropriate screens based on the loading or error states. The following code snippet demonstrates how to handle loading and error states along with providing a retry option:</p>\n<div class=\"gatsby-highlight\" data-language=\"kotlin\"><pre class=\"language-kotlin\"><code class=\"language-kotlin\"><span class=\"token keyword\">when</span> <span class=\"token punctuation\">{</span>\n   movies<span class=\"token punctuation\">.</span><span class=\"token function\">isLoading</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">-></span> <span class=\"token function\">LoadingScreen</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n   movies<span class=\"token punctuation\">.</span><span class=\"token function\">isError</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">-></span> ErrorScreen <span class=\"token punctuation\">{</span> movies<span class=\"token punctuation\">.</span><span class=\"token function\">retry</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token punctuation\">}</span>\n   <span class=\"token keyword\">else</span> <span class=\"token operator\">-></span> <span class=\"token function\">MoviesGrid</span><span class=\"token punctuation\">(</span>movies <span class=\"token operator\">=</span> movies<span class=\"token punctuation\">,</span> onClick <span class=\"token operator\">=</span> viewModel<span class=\"token operator\">::</span>onMovieClick<span class=\"token punctuation\">)</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>Additionally, <code class=\"language-text\">LazyPagingItems</code> offers a convenient <code class=\"language-text\">retry()</code> method, allowing you to retry the last failed data loading attempt with ease.</p>\n<h3>Adding filters</h3>\n<p>Now lets add filters to our movie list to see paginated list of movies filtered by genre.</p>\n<p><img src=\"/static/paging_demo_filters-ff80a7606fb30961e2bda56bd6ed9985.gif\" alt=\"Filters UI\"></p>\n<p>We start with requesting genres from the server and saving them in our database. We will keep genre data and selection in the database so that we can observe it from our domain and UI layers.</p>\n<div class=\"gatsby-highlight\" data-language=\"kotlin\"><pre class=\"language-kotlin\"><code class=\"language-kotlin\"><span class=\"token keyword\">interface</span> MoviesApi <span class=\"token punctuation\">{</span>\n  <span class=\"token annotation builtin\">@GET</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"/3/genre/movie/list?language=en-US\"</span><span class=\"token punctuation\">)</span>\n  <span class=\"token keyword\">suspend</span> <span class=\"token keyword\">fun</span> <span class=\"token function\">genres</span><span class=\"token punctuation\">(</span><span class=\"token annotation builtin\">@Query</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"api_key\"</span><span class=\"token punctuation\">)</span> api_key<span class=\"token operator\">:</span> String<span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> Response<span class=\"token operator\">&lt;</span>GenresNetworkResponse<span class=\"token operator\">></span>\n<span class=\"token punctuation\">}</span>\n\n<span class=\"token annotation builtin\">@Dao</span>\n<span class=\"token keyword\">interface</span> GenresDao <span class=\"token punctuation\">{</span>\n\n  <span class=\"token annotation builtin\">@Insert</span><span class=\"token punctuation\">(</span>onConflict <span class=\"token operator\">=</span> OnConflictStrategy<span class=\"token punctuation\">.</span>REPLACE<span class=\"token punctuation\">)</span>\n  <span class=\"token keyword\">suspend</span> <span class=\"token keyword\">fun</span> <span class=\"token function\">putGenres</span><span class=\"token punctuation\">(</span>genres<span class=\"token operator\">:</span> List<span class=\"token operator\">&lt;</span>GenreDbEntity<span class=\"token operator\">></span><span class=\"token punctuation\">)</span>\n\n  <span class=\"token annotation builtin\">@Upsert</span><span class=\"token punctuation\">(</span>entity <span class=\"token operator\">=</span> GenreDbEntity<span class=\"token operator\">::</span><span class=\"token keyword\">class</span><span class=\"token punctuation\">)</span>\n  <span class=\"token keyword\">suspend</span> <span class=\"token keyword\">fun</span> <span class=\"token function\">updateGenres</span><span class=\"token punctuation\">(</span>genres<span class=\"token operator\">:</span> List<span class=\"token operator\">&lt;</span>GenreDbUpdateEntity<span class=\"token operator\">></span><span class=\"token punctuation\">)</span>\n\n  <span class=\"token annotation builtin\">@Query</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"SELECT * FROM genres\"</span><span class=\"token punctuation\">)</span>\n  <span class=\"token keyword\">suspend</span> <span class=\"token keyword\">fun</span> <span class=\"token function\">getGenres</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> List<span class=\"token operator\">&lt;</span>GenreDbEntity<span class=\"token operator\">></span>\n\n  <span class=\"token annotation builtin\">@Query</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"SELECT * FROM genres\"</span><span class=\"token punctuation\">)</span>\n  <span class=\"token keyword\">fun</span> <span class=\"token function\">observeGenres</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> Flow<span class=\"token operator\">&lt;</span>List<span class=\"token operator\">&lt;</span>GenreDbEntity<span class=\"token operator\">></span><span class=\"token operator\">></span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>Now we can add <code class=\"language-text\">with_genres</code> parameter to our network request:</p>\n<div class=\"gatsby-highlight\" data-language=\"kotlin\"><pre class=\"language-kotlin\"><code class=\"language-kotlin\">  <span class=\"token annotation builtin\">@GET</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"/3/discover/movie?language=en-US&amp;sort_by=popularity.desc\"</span><span class=\"token punctuation\">)</span>\n  <span class=\"token keyword\">suspend</span> <span class=\"token keyword\">fun</span> <span class=\"token function\">discover</span><span class=\"token punctuation\">(</span>\n    <span class=\"token annotation builtin\">@Query</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"page\"</span><span class=\"token punctuation\">)</span> page<span class=\"token operator\">:</span> Int<span class=\"token punctuation\">,</span>\n    <span class=\"token annotation builtin\">@Query</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"with_genres\"</span><span class=\"token punctuation\">)</span> genres<span class=\"token operator\">:</span> String<span class=\"token punctuation\">,</span>\n    <span class=\"token annotation builtin\">@Query</span><span class=\"token punctuation\">(</span><span class=\"token string\">\"api_key\"</span><span class=\"token punctuation\">)</span> api_key<span class=\"token operator\">:</span> String<span class=\"token punctuation\">,</span>\n  <span class=\"token punctuation\">)</span><span class=\"token operator\">:</span> Response<span class=\"token operator\">&lt;</span>MoviesNetworkResponse<span class=\"token operator\">></span></code></pre></div>\n<p>Remote mediator will get selected genres from the repository</p>\n<div class=\"gatsby-highlight\" data-language=\"kotlin\"><pre class=\"language-kotlin\"><code class=\"language-kotlin\">  <span class=\"token operator\">..</span><span class=\"token punctuation\">.</span>\n  <span class=\"token keyword\">val</span> genres <span class=\"token operator\">=</span> repository<span class=\"token punctuation\">.</span><span class=\"token function\">getGenreSelection</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n  <span class=\"token keyword\">val</span> movies <span class=\"token operator\">=</span> moviesDataSource<span class=\"token punctuation\">.</span><span class=\"token function\">getMovies</span><span class=\"token punctuation\">(</span>page<span class=\"token punctuation\">,</span> genres<span class=\"token punctuation\">)</span>\n  <span class=\"token operator\">..</span><span class=\"token punctuation\">.</span></code></pre></div>\n<p>Every time user selects a new filter we stave it in the database and restart pagination by clearing movies in the database and resetting the page to 1.</p>\n<div class=\"gatsby-highlight\" data-language=\"kotlin\"><pre class=\"language-kotlin\"><code class=\"language-kotlin\"><span class=\"token keyword\">class</span> MoviesScreenViewModel <span class=\"token punctuation\">{</span>\n\n  <span class=\"token keyword\">fun</span> <span class=\"token function\">onGenreClick</span><span class=\"token punctuation\">(</span>genreItem<span class=\"token operator\">:</span> GenreItem<span class=\"token punctuation\">)</span> <span class=\"token operator\">=</span> viewModelScope<span class=\"token punctuation\">.</span><span class=\"token function\">launch</span> <span class=\"token punctuation\">{</span>\n    genresProvider<span class=\"token punctuation\">.</span><span class=\"token function\">setSelection</span><span class=\"token punctuation\">(</span>genreItem<span class=\"token punctuation\">.</span>id<span class=\"token punctuation\">,</span> <span class=\"token operator\">!</span>genreItem<span class=\"token punctuation\">.</span>isSelected<span class=\"token punctuation\">)</span>\n    repository<span class=\"token punctuation\">.</span><span class=\"token function\">clearMovies</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n  <span class=\"token punctuation\">}</span>\n  \n<span class=\"token punctuation\">}</span></code></pre></div>\n<div class=\"gatsby-highlight\" data-language=\"kotlin\"><pre class=\"language-kotlin\"><code class=\"language-kotlin\"><span class=\"token keyword\">class</span> MoviesRepositoryImpl <span class=\"token punctuation\">{</span>\n\n  <span class=\"token keyword\">override</span> <span class=\"token keyword\">suspend</span> <span class=\"token keyword\">fun</span> <span class=\"token function\">clearMovies</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span> <span class=\"token operator\">=</span> <span class=\"token function\">withContext</span><span class=\"token punctuation\">(</span>Dispatchers<span class=\"token punctuation\">.</span>IO<span class=\"token punctuation\">)</span> <span class=\"token punctuation\">{</span>\n    database<span class=\"token punctuation\">.</span><span class=\"token function\">withTransaction</span> <span class=\"token punctuation\">{</span>\n      database<span class=\"token punctuation\">.</span><span class=\"token function\">movies</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">clear</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span>\n      database<span class=\"token punctuation\">.</span><span class=\"token function\">remoteKey</span><span class=\"token punctuation\">(</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">.</span><span class=\"token function\">insertKey</span><span class=\"token punctuation\">(</span><span class=\"token function\">RemoteKey</span><span class=\"token punctuation\">(</span>MoviesDatabase<span class=\"token punctuation\">.</span>MOVIES_REMOTE_KEY<span class=\"token punctuation\">,</span> <span class=\"token number\">1</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">)</span>\n    <span class=\"token punctuation\">}</span>\n  <span class=\"token punctuation\">}</span>\n\n<span class=\"token punctuation\">}</span></code></pre></div>\n<h3>Links</h3>\n<p><br>Source code: <a href=\"https://github.com/auto1-oss/android-compose-paging\">https://github.com/auto1-oss/android-compose-paging</a></br>\n<br>Official documentation: <a href=\"https://developer.android.com/topic/libraries/architecture/paging/v3-overview\">https://developer.android.com/topic/libraries/architecture/paging/v3-overview</a></br></p>","fields":{"slug":"/android-compose-pagination/","tags":["auto1","mobile","Android"]}}}]}},"pageContext":{"slug":"/tags/Android","tag":"Android","categories":["Architecture","Coding","DevOps","Engineering","ProjectManagement","QA","Social","TechRadar"]}}