A comprehensive Android example demonstrating modern pagination implementation using Jetpack Compose, Paging 3 library, Room database, and Retrofit. This project showcases best practices for building scalable, offline-first applications with efficient data loading and caching strategies.
This application demonstrates a complete pagination solution by fetching beer data from the Punk API and displaying it in an infinite scrolling list. The app implements:
- Remote Mediator Pattern: Coordinates loading data from network and local database
- Offline-First Architecture: Uses Room database as the single source of truth
- Modern UI: Built entirely with Jetpack Compose
- Dependency Injection: Utilizes Dagger Hilt for clean dependency management
- β Infinite scrolling with automatic page loading
- β Offline support with local database caching
- β Pull-to-refresh functionality
- β Loading and error state handling
- β Clean architecture with separation of concerns
- β Type-safe API calls with Retrofit and Moshi
The project follows a clean architecture pattern with clear separation between layers:
app/src/main/kotlin/inc/kaizen/example/pagination/
βββ client/ # API models and service definitions
β βββ Beer.kt # Data model for API response
β βββ BeerApi.kt # Retrofit API interface
βββ database/ # Local persistence layer
β βββ BeerDao.kt # Room DAO for database operations
β βββ BeerDatabase.kt # Room database configuration
β βββ BeerEntity.kt # Database entity
β βββ Mappers.kt # Entity β Model conversion
βββ remote/ # Network coordination
β βββ BeerRemoteMediator.kt # Paging 3 RemoteMediator
βββ di/ # Dependency injection
β βββ AppModule.kt # Hilt module providing dependencies
βββ viewmodel/ # Presentation logic
β βββ BeerViewModel.kt # ViewModel with paging flow
βββ ui/ # UI components
βββ component/
β βββ BeerScreen.kt # Main composable screen
βββ theme/ # Material theme definitions
BeerApi (client/BeerApi.kt)
- Defines REST API endpoints using Retrofit
- Fetches beer data from Punk API with pagination parameters
BeerDao (database/BeerDao.kt)
- Provides database operations (insert, update, query, delete)
- Returns
PagingSource<Int, BeerEntity>for Paging 3 integration
BeerDatabase (database/BeerDatabase.kt)
- Room database configuration
- Manages database versioning and DAO access
BeerRemoteMediator (remote/BeerRemoteMediator.kt)
- Core pagination logic coordinating network and database
- Handles three load types:
REFRESH: Initial load, clears and reloads dataPREPEND: Load data before the first item (not used in this implementation)APPEND: Load next page when user scrolls to the end
- Calculates next page based on current state
- Manages error handling for network failures
AppModule (di/AppModule.kt)
- Provides singleton instances:
BeerDatabase: Room database instanceBeerApi: Retrofit service instancePager<Int, BeerEntity>: Configured Pager with 20 items per page
BeerViewModel (viewmodel/BeerViewModel.kt)
- Exposes
beerPagingFlowas StateFlow - Transforms database entities to UI models
- Caches paging data in ViewModel scope
BeerScreen (ui/component/BeerScreen.kt)
- Composable UI displaying beer list
- Handles loading states and errors
- Implements LazyColumn with paging items
- Android Studio: Hedgehog (2023.1.1) or newer
- JDK: Version 8 or higher
- Android SDK: API Level 24 (Android 7.0) or higher
- Internet Connection: Required for initial data fetch
The project uses the following key dependencies:
// Jetpack Compose
androidx.compose.ui:ui
androidx.compose.material:material:1.5.0
androidx.activity:activity-compose:1.7.2
// Paging 3
androidx.paging:paging-runtime-ktx:3.2.0
androidx.paging:paging-compose:3.2.0
// Room Database
androidx.room:room-ktx:2.5.2
androidx.room:room-paging:2.5.2
// Retrofit & Networking
com.squareup.retrofit2:retrofit:2.9.0
com.squareup.retrofit2:converter-moshi:2.9.0
// Dagger Hilt
com.google.dagger:hilt-android:2.45
androidx.hilt:hilt-navigation-compose:1.0.0
// Image Loading
io.coil-kt:coil-compose:2.2.2-
Clone the repository
git clone https://github.com/kaizen-inc/Pagination.git cd Pagination -
Open in Android Studio
- Launch Android Studio
- Select "Open an Existing Project"
- Navigate to the cloned repository and click "OK"
-
Sync Gradle
- Android Studio should automatically prompt to sync Gradle
- If not, click "File" β "Sync Project with Gradle Files"
-
Build the project
./gradlew build
-
Run the app
- Connect an Android device or start an emulator
- Click the "Run" button or press Shift + F10
- Select your device and wait for the app to install
- Initial Load: When the app starts,
RemoteMediatorfetches the first page (20 items) from the API - Database Storage: Items are stored in Room database
- UI Display: Compose UI observes the paging flow and displays items
- Load More: As user scrolls near the end, the next page is automatically fetched
- Offline Mode: If network is unavailable, app displays cached data from database
In AppModule.kt:
@Provides
@Singleton
fun provideBeerPager(
beerDatabase: BeerDatabase,
beerApi: BeerApi
): Pager<Int, BeerEntity> {
return Pager(
config = PagingConfig(pageSize = 20), // Load 20 items per page
remoteMediator = BeerRemoteMediator(beerDatabase, beerApi),
pagingSourceFactory = {
beerDatabase.dao.pagingSource() // Database is source of truth
}
)
}In MainActivity.kt:
val viewModel = hiltViewModel<BeerViewModel>()
val beers = viewModel.beerPagingFlow.collectAsLazyPagingItems()
BeerScreen(beers = beers)The BeerScreen composable handles different load states and displays items efficiently:
LazyColumn {
items(count = beers.itemCount) { index ->
val beer = beers[index]
if (beer != null) {
BeerItem(beer = beer)
}
}
// Handle loading state for next page
item {
if (beers.loadState.append is LoadState.Loading) {
CircularProgressIndicator()
}
}
}Modify the pageSize parameter in AppModule.kt:
config = PagingConfig(
pageSize = 20, // Items per page
enablePlaceholders = false,
prefetchDistance = 5 // Start loading when 5 items away from end
)Replace the API implementation in BeerApi.kt:
interface BeerApi {
@GET("your-endpoint")
suspend fun getItems(
@Query("page") page: Int,
@Query("per_page") pageCount: Int,
): List<YourModel>
companion object {
const val BASE_URL = "https://your-api.com/"
}
}Update corresponding entity, DAO, and mappers to match your data model.
- Min SDK: 24 (Android 7.0)
- Target SDK: 34 (Android 14)
- Compile SDK: 34
- Kotlin Version: 1.7.20
- Compose Compiler: 1.4.2
For production builds, configure proguard-rules.pro to keep necessary classes for Retrofit, Room, and Moshi.
The Room database is named beer.db and is configured with fallback to destructive migration during development. For production, implement proper migration strategies.
The project includes test structure for:
- Unit Tests: Located in
src/test/kotlin/ - Instrumented Tests: Located in
src/androidTest/kotlin/
To run tests:
# Run unit tests
./gradlew test
# Run instrumented tests (requires connected device/emulator)
./gradlew connectedAndroidTestContributions are welcome! Please follow these guidelines:
-
Fork the repository and create your feature branch
git checkout -b feature/your-feature-name
-
Follow code style conventions
- Use Kotlin coding conventions
- Format code with Android Studio's built-in formatter
- Add meaningful comments for complex logic
-
Write tests for new features or bug fixes
-
Commit your changes with descriptive commit messages
git commit -m "Add feature: description of your changes" -
Push to your fork and submit a pull request
git push origin feature/your-feature-name
-
Ensure CI checks pass and respond to code review feedback
- All submissions require review before merging
- Address feedback promptly
- Keep pull requests focused on a single feature or fix
Issue: Build fails with "Duplicate class" error
- Solution: Clean and rebuild project
./gradlew clean build
Issue: App crashes on startup
- Solution: Check that Hilt is properly set up and
@HiltAndroidAppannotation is on the Application class
Issue: Network requests fail
- Solution:
- Verify internet permission in
AndroidManifest.xml - Check that the device has internet connectivity
- Ensure the API endpoint is accessible
- Verify internet permission in
Issue: Database errors
- Solution:
- Uninstall and reinstall the app to clear database
- Check Room schema migrations if updating database structure
Issue: Items not loading
- Solution:
- Check Logcat for error messages
- Verify RemoteMediator is correctly configured
- Ensure PagingConfig parameters are appropriate
- Enable verbose logging in RemoteMediator for pagination debugging
- Use Android Studio's Database Inspector to view Room database contents
- Monitor network requests with Retrofit's logging interceptor
- Check load states in Compose UI to understand pagination status
- GitHub Issues: Report bugs or request features
- Stack Overflow: Tag questions with
android-pagingandjetpack-compose - Documentation:
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.
- Punk API for providing the free beer database API
- Android team for Jetpack Compose and Paging 3 libraries
- Open source community for valuable feedback and contributions
Built with β€οΈ using Jetpack Compose