README.md
A production-ready Android application demonstrating modern development practices and architectural patterns. This project showcases how to build scalable, maintainable, and testable Android applications using industry-standard tools and libraries.
Built with Clean Architecture principles, this app serves as a comprehensive example of modular design, advanced Gradle configuration, and robust CI/CD practices. Perfect for teams looking to establish solid architectural foundations for large-scale Android projects.
A music discovery app built with Jetpack Compose that displays album information sourced from the Last.fm API. The application demonstrates real-world scenarios including network requests, local caching, navigation, and state management.
Features:
Built with modern Android development tools and libraries, prioritizing, project structure stability and production-readiness.
Core Technologies:
Android Jetpack:
Networking & Images:
Dependency Injection:
Architecture:
UI & Design:
Testing:
Code Quality:
Build & CI:
GitHub Actions:
Gradle Plugins:
com.android.application) - Android app module configurationcom.android.library) - Android library module configurationorg.jetbrains.kotlin.android) - Kotlin compilation for Androidorg.jetbrains.kotlin.plugin.serialization) - JSON serialization supportorg.jetbrains.kotlin.plugin.compose) - Compose compiler pluginThe project implements Clean Architecture with a modular approach, treating each feature as an independent, reusable component similar to a microservice. This design enables maintainability and scalability for large development teams.
Benefits of Modular Architecture:
Module Types:
app - Main application module containing navigation setup, DI configuration, and app-level componentsfeature-* - Feature modules (album, profile, favourite) containing feature-specific business logicfeature-base - Shared foundation module providing common utilities and base classeslibrary-* - Utility modules for testing and shared functionalityClean Architecture is implemented at the module level - each module contains its own set of Clean Architecture layers:
Notice that the
appmodule andlibrary_xmodules structure differs a bit from the feature module structure.
Each feature module contains 3 layers with a distinct set of responsibilities and common module components.
This layer is closest to what the user sees on the screen.
The presentation layer mixes MVVM and MVI patterns:
MVVM - Jetpack ViewModel is used to encapsulate a common UI state. It exposes the state via observable state
holder (Kotlin Flow)MVI - action modifies the common UI state and emits a new state to a view via Kotlin FlowThe
common stateis a single source of truth for each view. This solution derives from Unidirectional Data Flow and Redux principles.
This approach facilitates the creation of consistent states. The state is collected via collectAsUiStateWithLifecycle
method. Flows collection happens in a lifecycle-aware manner, so
no resources are wasted.
Stated is annotated with Immutable annotation that is used by Jetpack compose to enable composition optimizations.
Components:
Kotlin Flow). Compose transform state (emitted by Kotlin
Flow) into application UI Consumes the state and transforms it into application UI (via Jetpack Compose). Pass user
interactions to ViewModel. Views are hard to test, so they should be as simple as possible.Kotlin Flow) view state changes to the view and deals with user interactions (these
view models are not simply POJO classes).NavHostActivity (instead of
separately, inside each view)This is the core layer of the application. Notice that the domain layer is independent of any other layers. This
allows making domain models and business logic independent from other layers. In other words, changes in other layers
will not affect the domain layer eg. changing the database (data layer) or screen UI (presentation layer) ideally will
not result in any code change within the domain layer.
Components:
domain layer independent from
the data layer (Dependency inversion).Encapsulates application data. Provides the data to the domain layer eg. retrieves data from the internet and cache the
data in disk cache (when the device is offline).
Components:
domain layer. Depending on the application structure and quality of the
external API repository can also merge, filter, and transform the data. These operations intend to create
a high-quality data source for the domain layer. It is the responsibility of the Repository (one or more) to construct
Domain models by reading from the Data Source and accepting Domain models to be written to the Data Sourcedata model to domain model (to keep domain layer independent from the data layer).This application has two Data Sources - Retrofit (used for network access) and Room (local storage used to access
device persistent memory). These data sources can be treated as an implicit sub-layer. Each data source consists of
multiple classes:
ApiModels)Response Model)Both Retrofit API Data Models and Room Entities contain annotations, so the given framework understands how to parse the
data into objects.
Each module in the Android project contains several standard items that provide essential functionality and configuration:
Components:
build.gradle.kts defining dependencies, build configurations, and plugins.test/) and integration tests (androidTest/)res/) including strings, drawables, and assets.AndroidManifest.xml file declaring module metadata.The below diagram presents application data flow when a user interacts with the album list screen:
Tags (LogTags) help filter and identify different types of logs during development and debugging.
The app provides detailed logging for development and debugging, with each log easily filterable by its tag:
Navigation - Navigation events and route changes
Action - User actions and UI state modifications
Network - Network requests, responses, and HTTP-related logs
Thanks to Easylauncher Gradle plugin the debug build has custom icon label:
App supports Themed Icons.
Left (classic icon), Right (themed icon):
Gradle versions catalog is used as a centralized dependency management third-party dependency coordinates (group, artifact, version) are shared across all modules (Gradle projects and subprojects).
Gradle versions catalog consists of a few major sections:
[versions] - declare versions that can be referenced by all dependencies[libraries] - declare the aliases to library coordinates[bundles] - declare dependency bundles (groups)[plugins] - declare Gradle plugin dependenciesEach module uses convention a plugin, so common dependencies are shared without the need to add them explicitly in each module.
Convention plugins standardize build configuration across modules by encapsulating common build logic into reusable plugins:
Application Convention - Main application module configuration with Android app setupFeature Convention - Feature module configuration combining library and Kotlin conventionsLibrary Convention - Android library module setup with common Android configurationLotlin Convention - Kotlin compilation settings, toolchain, and compiler optionsTest Convention - Testing framework setup (JUnit, test logging, and test configurations)Test Library Convention - Testing setup specifically for library modulesDetekt Convention - Static code analysis configuration with DetektSpotless Convention - Code formatting and style enforcement with SpotlessEasylauncher Convention - App icon customization for different build variantsAboutLibraries Convention - About libraries configurationEnables type-safe project references instead of error-prone string-based module paths:
// Before
implementation(project(":feature:album"))
// After
implementation(projects.feature.album)
All dependency and Gradle plugin versions are defined in the TOML version catalog file (libs.versions.toml).
The Java/JVM version is centralized across the project.
It is defined once in libs.versions.toml file under the java entry.
The generateJavaBuildConfig task reads this value and generates a JavaBuildConfig.kt file with constants.
These constants are then used in Gradle convention plugins to configure both Java and Kotlin consistently:
compileOptions {
sourceCompatibility = JavaBuildConfig.JAVA_VERSION
targetCompatibility = JavaBuildConfig.JAVA_VERSION
}
kotlin {
compilerOptions {
jvmTarget = JavaBuildConfig.jvmTarget
}
jvmToolchain(JavaBuildConfig.jvmToolchainVersion)
}
build-logic moduleThe build-logic module provides type-safe access to version catalogs from within precompiled script plugins.
This is enabled via the versionCatalogs block in build-logic/settings.gradle.kts, which references the main version catalog file and implementation(files(libs.javaClass.superclass.protectionDomain.codeSource.location)) dependency in build-logic/build.gradle.kts file.
This setup allows you to use catalog dependencies in plugins, for example:
add("implementation", libs.timber)
Additionally, extensions defined in DependencyHandlerScope make the syntax more natural and equivalent to standard Gradle usage:
implementation(libs.timber)
Enabled Gradle Configuration Cache.
Quality Checks:
./gradlew konsist-test:test --rerun-tasks # Architecture & convention validation
./gradlew lintDebug # Android lint analysis
./gradlew detektCheck # Code complexity & style analysis
./gradlew spotlessCheck # Code formatting verification
./gradlew testDebugUnitTest -x konsist-test:test # Unit test execution (without Konsist tests)
./gradlew connectedCheck # UI test execution (WIP)
./gradlew :app:bundleDebug # Production build verification
Auto-fix Commands:
./gradlew detektApply # Apply Detekt formatting fixes
./gradlew spotlessApply # Apply code formatting fixes
./gradlew lintDebug # Update lint baseline
GitHub Actions workflows execute quality checks automatically:
Configuration: .github/workflows
Optional Git hooks can execute quality checks before pushing code, providing fast feedback during development.
This showcase prioritizes architecture, tooling, and development practices over complex UI design. The interface uses Material Design 3 components but remains intentionally straightforward to focus on the underlying technical implementation.
Prerequisites:
Setup:
# Clone the repository
git clone https://github.com/igorwojda/android-showcase.git
# Open in Android Studio
# File -> Open -> Select cloned directory
Recommended IDE Plugins:
Active development continues with focus on modern Android practices. View planned enhancements and contribute ideas.
Development Tools:
Recommended Projects:
Contributions are welcome! Please check the CONTRIBUTING.md guidelines before submitting PRs.
Areas for Contribution:
Igor Wojda - Senior Android Engineer
MIT License
Copyright (c) 2025 Igor Wojda
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial
portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Flowing animations are distributed under Creative Commons License 2.0: