Android Showcase project presents a modern approach to Android application development. It is a complete sample of a fully functional Android application.
Project is utilizing modular, scalable, maintainable, and testable architecture, leading tech-stack and demonstrates the best development practices.
This application may look simple, but it has all the pieces that will provide the rock-solid foundation for the larger application suitable for bigger teams during extended application lifecycle.
This project is being maintained to stay up to date with leading industry standards. Please check the CONTRIBUTING page if you want to help.
The android-showcase
displays information about music albums. The data is loaded from
the Last.fm Music Discovery API.
The app has a few screens located in multiple feature modules:
- Album list screen - displays list of albums
- Album detail screen - display information about the selected album
- Profile screen - empty (WiP)
- Favourites screen - empty (WiP)
This project takes advantage of best practices, and many popular libraries and tools in the Android ecosystem. Most of the libraries are in the stable version unless there is a good reason to use non-stable dependency.
- Tech-stack
- 100% Kotlin
- Coroutines - perform background operations
- Kotlin Flow - data flow across all app layers, including views
- Kotlin Symbol Processing - enable compiler plugins
- Kotlin Serialization - parse JSON
- Retrofit - networking
- Jetpack
- Navigation - in-app navigation
- Lifecycle - perform an action when lifecycle state changes
- ViewModel - store and manage UI-related data in a lifecycle-aware way
- Room - store offline cache
- Koin - dependency injection (dependency retrieval)
- Coil - image loading library
- Lottie - animation library
- 100% Kotlin
- Modern Architecture
- Clean Architecture (at feature module level)
- Single activity architecture using Navigation component
- MVVM + MVI (presentation layer)
- Android Architecture components (ViewModel , Kotlin Flow , Navigation)
- Android KTX - Jetpack Kotlin extensions
- CI
- GitHub Actions
- Automatic PR verification including tests, linters, and 3rd online tools
- Testing
- Unit Tests (JUnit 5 via android-junit5)
- UI Tests (Espresso)
- Mockk - mocking framework
- Kluent - assertion framework
- UI
- Material design
- Reactive UI
- Static analysis tools
- Ktlint - verify code formatting
- Detekt - verify code complexity and code smells
- Androd Lint - verify Android platform usage
- Gradle
- Gradle Kotlin DSL - define build scripts
- Custom tasks
- Gradle Plugins
- Android Gradle - standard Android Plugins
- Test Logger - format test logs
- SafeArgs - pass data between navigation destinations
- Android-junit5 - use JUnit 5 with Android
- Versions catalog - define dependencies
- Type safe accessors
- GitHub Apps
- Revonate - dependency update boot
By dividing a problem into smaller and easier to solve sub-problems, we can reduce the complexity of designing and maintaining a large system. Each module is independent build-block serving a clear purpose. We can think about each feature as the reusable component, equivalent of microservice or private library.
The modularized code-base approach provides a few benefits:
- reusability - enable code sharing and building multiple apps from the same foundation. Apps should be a sum of their
- features where the features are organized as separate modules.
- separation of concerns - each module has a clear API. Feature-related classes live in different modules and can't be referenced without explicit module dependency. We strictly control what is exposed to other parts of your codebase.
- features can be developed in parallel eg. by different teams
- each feature can be developed in isolation, independently from other features
- faster build time
This diagram presents dependencies between project modules (Gradle sub-projects).
We have three kinds of modules in the application:
app
module - this is the main module. It contains code that wires multiple modules together (dependency injection setup,NavHostActivity
, etc.) and fundamental application configuration (retrofit configuration, required permissions setup, custom application class, etc.).- application-specific
library_x
modules that some of the features could depend on. This is helpful if you want to share some assets or code only between a few feature modules (currently app has no such modules) - feature modules - the most common type of module containing all code related to a given feature.
Clean architecture
is the "core architecture" of the application, so each feature module
contains its own set of
Clean architecture layers:
Notice that the
app
module andlibrary_x
modules structure differs a bit from the feature module structure.
Each feature module contains non-layer components and 3 layers with a distinct set of responsibilities.
This layer is closest to what the user sees on the screen. The presentation
layer is a mix of MVVM
(
Jetpack ViewModel
used to preserve data across activity restart) and
MVI
(actions
modify the common state
of the view and then a new state is edited to a view via LiveData
to be
rendered).
common state
(for each view) approach derives from Unidirectional Data Flow and Redux principles.
Components:
- View (Fragment) - presents data on the screen and passes user interactions to View Model. Views are hard to test, so they should be as simple as possible.
- ViewModel - dispatches (through Kotlin Flow) state changes to the view and deals with user interactions (these view models are not simply POJO classes).
- ViewState - common state for a single view
- NavManager - singleton that facilitates handling all navigation events inside
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 domain
layer eg. changing the database (data
layer) or screen UI (presentation
layer) ideally will
not result in any code change withing the domain
layer.
Components:
- UseCase - contains business logic
- DomainModel - defines the core structure of the data that will be used within the application. This is the source of truth for application data.
- Repository interface - required to keep the
domain
layer independent from thedata layer
(Dependency inversion).
Manages application data. Connect to data sources and provide data through repository to the domain
layer eg. retrieve
data from the internet and cache the data in disk cache (when device is offline).
Components:
-
Repository is exposing data to the
domain
layer. Depending on the application structure and quality of the external APIs repository can also merge, filter, and transform the data. These operations intend to create high-quality data source for thedomain
layer, not to perform any business logic (domain
layeruse case
responsibility). -
Mapper - maps
data model
todomain model
(to keepdomain
layer independent from thedata
layer). -
RetrofitService - defines a set of API endpoints.
-
DataModel - defines the structure of the data retrieved from the network and contains annotations, so Retrofit ( Moshi) understands how to parse this network data (XML, JSON, Binary...) this data into objects.
The below diagram presents application data flow when a user interacts with the album list screen
:
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).
All of the dependencies are stored in the libs.versions.toml file (default location). The TOML file 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 dependencies
Each feature module depends on the feature_base
module, so dependencies are shared without the need to add them
explicitly in each feature module.
Project enables the TYPESAFE_PROJECT_ACCESSORS
experimental Gradle feature to generate type safe accessors to refer
other projects.
// Before
implementation(project(":feature_album"))
// After
implementation(projects.featureAlbum)
CI is utilizing GitHub Actions. Complete GitHub Actions config is located in the .github/workflows folder.
Series of workflows run (in parallel) for every opened PR and after merging PR to the main
branch:
./gradlew lintDebug
- runs Android lint./gradlew detektCheck
- runs detekt and ktlint./gradlew testDebugUnitTest
- run unit tests./gradlew connectedCheck
- run UI tests./gradlew :app:bundleDebug
- create app bundle
Read related articles to have a better understanding of underlying design decisions and various trade-offs.
- Multiple ways of defining Clean Architecture layers
- More coming soon
./gradlew wrapper --gradle-version=1.2.3
The interface of the app utilizes some of the modern material design components, however, is deliberately kept simple to focus on application architecture and project config.
Checklist of all upcoming enhancements .
There are a few ways to open this project.
Android Studio
->File
->New
->From Version control
->Git
- Enter
https://github.com/igorwojda/android-showcase.git
into URL field and pressClone
button
- Run
git clone https://github.com/igorwojda/android-showcase.git
command to clone the project - Open
Android Studio
and selectFile | Open...
from the menu. Select cloned directory and pressOpen
button
Here are few additional resources.
- Core App Quality Checklist - learn about building the high-quality app
- Android Ecosystem Cheat Sheet - board containing 200+ most important tools
- Kotlin Coroutines - Use Cases on Android - most popular coroutine usages
Other high-quality projects will help you to find solutions that work for your project (random order):
- Iosched - official Android application from google IO 2019
- Android Architecture Blueprints v2 - a showcase of various Android architecture approaches
- Now Android - fully functional Android app built entirely with Kotlin and Jetpack Compose
- Android sunflower complete
Jetpack
sample covering all libraries - GithubBrowserSample - multiple small projects demonstrating usage of Android Architecture Components
- Plaid - a showcase of Android material design
- Clean Architecture boilerplate - contains nice diagrams of Clean Architecture layers
- Android samples - official Android samples repository
- Roxie - solid example of
common state
approach together with very good documentation - Kotlin Android template - the template that lets you create preconfigured Android Kotlin project in a few seconds.
- whatsApp-clone-compose - WhatsApp clone app built with Jetpack Compose and Stream Chat SDK for Compose
- compose-samples - repository contains a set of individual Android Studio projects to help you learn about Compose in Android
- Dynamic feature module is not supported by ANDROID_TEST_USES_UNIFIED_TEST_PLATFORM yet.
- ktlint
FileName
rule has to be disabled, becasue it is not compatible with fie containg a single extension ISSUE-1657 - Delegate import is not provided when a variable has the same name as Delegate (KTIJ-17403)
- Correct code is marked as an error in
build.gradle.kts
files when usinglibs
from the Gradle Version Catalog (KTIJ-19370, KTIJ-19585) - ktlint import-ordering rule conflicts with IDE default formatting rule, so it have to be disabled. and KTIJ-16847)
- False positive "Unused symbol" for a custom Android application class referenced in AndroidManifest.xml file (KT-27971)
- Android lint complains about exceeding access rights to ArchTaskExecutor (Issue 79189568)
- JUnit 5 does not support tests with suspended modifier (Issue 1914)
Want to contribute? Check the Contributing docs.
MIT License
Copyright (c) 2019 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
:
- Error screen by Chetan Potnuru
- Building Screen by Carolina Cajazeira