The PixabayClient app uses Pixabay API. The API requires a key to
search images. The key can be obtained
from Pixabay API's documentation by registering in Pixabay. Add the
key to ./local.properties
file as pixabay.api.key=KEY
property.
The required minimum API level is 23.
- Home screen with search interface of Pixabay API
- User-friendly search results presented on StaggeredGrid that dynamically adjust cells to the available space during screen orientation changes
- Dynamic pages loading while scrolling
- Caching search results in a local database
- Load and cache images on-device storage
- Image details screen with larger image and stats accessible from the list
- Dynamic theming based on the system theme (light or dark)
video.mp4
The PixabayClient app is built on top of layered architecture that follows uni-directional data flow, drives the separation of concerns and focuses on making testing easier. MVVM pattern is used to separate the UI and business logic. The architecture has clearly defined UI, data and domain layer reflected in data, domain ad ui packages. The data layer expose interfaces that can be easily faked in tests. A dependency injection pattern is used to provide the dependencies to the abstract components making them easier to maintain and test. Coroutines and flows are used to handle asynchronous operations and provide data streams. Taking into account the size of the project it was implemented as a single module.
The data layer is built with a Repository pattern that exposes data stored in the Room database fetched from API. Defined interfaces provide a clear abstraction for the data sources. Each search results are paged by 40 items per page and loaded from the network with the help of Paging 3.
Pixabay API requires caching requests for 24h to avoid unnecessary network calls. The app uses RemoteMediator implementation to fetch, cache and invalidate the search results for a given query. The cache is cleared for a given query when the user resends the search request and data stored in the cache is older than 24 hours.
Validate the cached data and initially refresh it when needed, load next pages from network and store them in the database cache. The mediator is used by the PagingSource to provide the data to the UI layer. Based on the state of the anchor position(scroll position) and currently loaded pages, the mediator decides whether to load the next page and how to append new items to the stream presented in the UI.
Following the Clean Architecture principles the domain layer is built with use cases that provide better separation of concerns. The use cases use repository interfaces defined in the data layer. Data models are converted to domain models in the domain layer. The use cases are used by the view models to provide the data to the UI layer.
AAC ViewModels are used to manage UI state in a lifecycle conscious way with the support of persisting it through configuration changes. Jetpack Compose is used to build the UI layer. The UI is built with a single activity. Navigation was implemented with Jetpack Navigation Hilt extension is used to provide the ViewModel instances in composable functions.
In favor of fakes over the mocks, dependencies of repositories, data sources, mediators or time/cache providers are defined as abstractions. Data/business logic layers are unit tested with JUnit, Mockk and Robolectric. DBCachedImagesResultsMediatorTest is an example of instrumented test that checks the behavior of the mediator interacting with Room database.
CI is implemented with GitHub Actions. The on-pull-request.yml workflow has defined two jobs responsible for running the tests and linting the code on every pull request. Both jobs run in parallel.
All the libraries and plugins used in the project are listed in the libs.versions.toml file. Some of the libraries used in the Application are:
- Dagger Hilt - Dependency injection framework recommended for Android projects
- Glide - Image loading with cache mechanism focused on smooth scrolling
- Jetpack Compose - Modern UI toolkit
- Jetpack navigation - Navigation component with support for deep linking and jetpack compose
- Kotlin Coroutines - Asynchronous programming
- Kotlin Flows - Asynchronous data streams
- Material 3 - Material Design 3 components
- Mockk - Mocking library for Kotlin
- Moshi - API responses JSON parsing
- Paging 3 - Pagination with Jetpack Compose and Room integrations
- Retrofit - Type-safe HTTP client
- Robolectric - Unit testing framework
- Room - Cache Database with Paging 3 integration