ComicVineAppSample is a sample Android application that explores modern Android development around the Comic Vine API.
The project focuses on a small but complete feature set:
- character listing with pagination
- character search
- character detail
- local favorites
- light, dark, and automatic theme modes
The codebase is organized as a multi-module project and follows a clean separation between UI, domain, and data concerns.
- Overview
- Features
- Architecture
- Modules
- Tech Stack
- Data Flow
- Testing
- Project Setup
- Quality Checks
This app fetches Comic Vine characters from the network, persists them locally with Room, stores UI configuration with DataStore, and exposes everything to the UI using Kotlin Flow.
The current implementation uses an offline-first approach for the main flows:
- the UI observes Room as the source of truth
- the repository refreshes data from the API when needed
- detail data enriches the local cache when long descriptions are missing
- favorites are updated locally and reflected automatically through Room emissions
- Browse Comic Vine characters with paginated loading
- Search characters by name from the current cached list
- Open a character detail screen with long description fallback
- Save and remove favorites locally
- Reactive favorites screen backed by Room Flow
- Theme switching with light, dark, and follow-system modes
- Android Splash Screen API integration
The app is structured around:
MVVMfor presentation logicClean Architecturestyle boundaries between UI, domain, and dataSingle Activity + Navigation ComponentOffline-first repository flowsfor list, detail, and favorites
At a high level:
- The UI collects
StateFlowfrom theViewModel. - The
ViewModelcoordinates use cases and maps domain models into display models. - The domain layer defines business models, repositories, and use cases.
- The data layer defines repository implementations and datasource contracts.
- Infrastructure modules implement those contracts for Room, Retrofit, and DataStore.
- Room acts as the local source of truth, while Retrofit refreshes the cache when necessary.
Android-specific layer containing:
- Activities and Fragments
- ViewModels
- UI display models and mappers
- application startup and navigation
- Hilt entry point and app-level composition
Pure business layer containing:
- domain models
- repository contracts
- use cases
- failure abstractions
Implementation layer containing:
- repository implementations
- datasource contracts
- repository Hilt bindings
Infrastructure module containing:
- Retrofit services
- network datasource implementation
- OkHttp interceptors
- network-specific mappers
- Hilt bindings for remote datasource
Infrastructure module containing:
- Room database and DAO
- local datasource implementation
- entity mappers
- Hilt bindings for local character datasource
Infrastructure module containing:
- DataStore preferences implementation
- theme persistence mapping
- Hilt bindings for local configuration datasource
Shared test support module containing:
- test builders for domain and fixture models
- fake repository implementations for unit tests
- reusable helpers consumed by feature and core module tests
KotlinCoroutinesandFlowViewModelNavigation Componentwith Safe ArgsRoomRetrofitOkHttpKotlinx SerializationHiltArrow EitherDataStoreGlideLottieSwipeRevealLayoutDetektJUnit4AndroidX TestMockWebServer
MainViewModelobserves characters through a use case- the repository exposes Room data as
Flow - on first collection, the repository refreshes from the API
- paginated requests update Room and the UI reacts automatically
DetailViewModelobserves one character from Room- if the cached entry does not have the long description, the repository fetches the detail endpoint
- the local entity is updated and the UI refreshes automatically
- favorites are stored locally in Room
- the favorites screen observes
Flow<List<...>> - removing a favorite updates the database and the screen reacts without manual reloads
The project is organized so dependency direction stays consistent:
:appdepends on:domain,:data, and the:core:*modules as the Hilt composition root:datadepends on:domain:core:networkdepends on:dataand:domain:core:databasedepends on:dataand:domain:core:datastoredepends on:dataand:domain
This means datasource contracts live in :data, while concrete implementations live in the corresponding :core:* modules.
+------------------+
| :app |
| UI + Hilt root |
+---------+--------+
|
+---------------+---------------+
| |
v v
+-------------+ +---------------+
| :domain | | :data |
| use cases |<---------------| repos + ports |
| models | +-------+-------+
+-------------+ |
|
+---------------------+---------------------+
| | |
v v v
+----------------+ +----------------+ +-----------------+
| :core:network | | :core:database | | :core:datastore |
| Retrofit/OkHttp| | Room | | DataStore |
+----------------+ +----------------+ +-----------------+
app/src/main/java/.../ui: screens, adapters, UI mappers, ViewModelsapp/src/test/java/...: app unit testsapp/src/androidTest/java/...: repository integration tests and test DIdomain/src/main/java/...: models, repository interfaces, use cases, failuresdomain/src/test/java/...: use case unit testsdata/src/main/java/.../datasource: datasource contractsdata/src/main/java/.../repository: repository implementationscore/*/src/main/java/.../di: Hilt modules and bindingscore/*/src/main/java/.../datasource: concrete datasource implementationscore/*/src/test/java/...: core unit testscore/*/src/androidTest/java/...: Android integration tests where framework components are requiredtesting/src/main/java/...: shared builders and fake repositories
The project now includes both unit and integration coverage across modules.
:domaincovers use cases:appcovers ViewModels and UI mappers:core:networkcovers response mapping and remote datasource behavior:core:databasecovers entity mapping and database utility wrappers:core:datastorecovers preference mapping and local configuration persistence
Run all JVM unit tests with:
./gradlew testRun a specific module, for example:
./gradlew :app:testDebugUnitTest
./gradlew :domain:test
./gradlew :core:network:testDebugUnitTest:app:androidTestverifies repository behavior end to end with Hilt test modules, in-memory Room, test DataStore, and MockWebServer:core:database:androidTestverifies DAO behavior against a real Room database
Run integration tests with:
./gradlew :app:connectedDebugAndroidTest
./gradlew :core:database:connectedDebugAndroidTest- network fixtures live under
app/src/androidTest/assetsandcore/network/src/test/resources - the
:testingmodule centralizes builders and fake repositories to keep tests consistent and reduce duplication
- Android Studio with recent Android Gradle Plugin support
- JDK 17
This project requires a Comic Vine API key.
Create or update local.properties in the project root with:
COMIC_VINE_API_KEY=<your_api_key>You can get an API key from:
To compile the app:
./gradlew :app:compileDebugKotlinRun Detekt:
./gradlew detektCommon local verification commands:
./gradlew test
./gradlew :app:connectedDebugAndroidTest
./gradlew :core:database:connectedDebugAndroidTest
./gradlew detekt