The Android Showcase project exemplifies modern Android application development methodologies and provides comprehensive architectural guidance. By integrating popular development tools, libraries, linters, and Gradle plugins, along with robust testing frameworks and Continuous Integration (CI) setup, this project offers a holistic sample of a fully operational Android application.
The primary focus of this project lies in promoting a modular, scalable, maintainable, and testable architecture. It incorporates a leading-edge tech-stack and embodies the finest practices in software development. While the application may appear straightforward, it encompasses all the crucial components that lay the groundwork for a robust, large-scale application.
The design principles and architectural choices applied in this project are ideally suited for larger teams and extended application lifecycles. This application is not just about showcasing functionalities, but it is also a testament to how well-structured and well-written code serves as a stable backbone for scalable and maintainable software development projects.
The android-showcase is a simple application that presents information about various music albums. This data is dynamically sourced from the Last.fm music platform API.
The app has a few screens located in multiple feature modules:
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.
debug
builds.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 an independent build block serving a clear purpose. We can think about each feature as a reusable component, the equivalent of microservice or private library.
The modularized code-base approach provides a few benefits:
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 (class, dependency
injection setup, NavHostActivity
, etc.) and fundamental application configuration (retrofit configuration, required
permissions setup, custom Application
class, etc.).feature_x
modules - the most common type of module containing all code related to a given feature.
share some assets or code only between feature
modules (currently app has no such modules)feature_base
modules that feature modules depend on to share a common code.Clean Architecture
is implemented at the module level - each 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 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 Flow
The
common state
is 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 Source
data 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.
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 settings.gradle.kts file (default location). 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 feature module depends on the feature_base
module, so dependencies are shared without the need to add them
explicitly in each feature module.
The 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)
To facilitate debuting project contains logs. You can filter logs to understand app flow. Keywords:
onCreate
see what Activities
and Fragments
have been createdAction
- filter all actions performed on the screens to update the UIHttp
- debug network requests and responsesCI is utilizing GitHub Actions. The 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 konsist_test:test --rerun-tasks
- checks that source code satisfies Konsist rules./gradlew lintDebug
- checks that source code satisfies Android lint rules./gradlew detektCheck
- checks that sourcecode satisfies detekt rules./gradlew spotlessCheck
- checks that source code satisfies formatting steps../gradlew testDebugUnitTest
- run unit tests./gradlew connectedCheck
- run UI tests./gradlew :app:bundleDebug
- create an application bundleThe following tasks cab be executed locally to make codebase compliant with the rules:
./gradlew detektApply
- applies detekt code formatting rules to sourcecode in-place./gradlew spotlessApply
- applies code formatting steps to the source code in place.Read related articles to have a better understanding of underlying design decisions and various trade-offs.
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.
There are a few ways to open this project.
Android Studio
-> File
-> New
-> From Version control
-> Git
https://github.com/igorwojda/android-showcase.git
into URL field and press Clone
buttongit clone https://github.com/igorwojda/android-showcase.git
command to clone the projectAndroid Studio
and select File | Open...
from the menu. Select the cloned directory and press Open
buttonIt is recommended to install Detekt to Android Studio. To configure
the plugin open Android Studio preferences, open Tools
, open Detekt
and add detekt.yml configuration file.
This project is under active development and it is being occasionally refined.
Check the list of all upcoming enhancements.
Here are a few additional resources.
Other high-quality projects will help you to find solutions that work for your project (random order):
common state
approach together with very good
documentationbuildSrc
directory, so dependencies and
versions have to be retrieved using type unsafe API:
kotlinCompilerExtensionVersion
) are retrieved using string version namessuspended
Kotlin invoke
operator (KTIJ-1053)Material You Dynamic Colors
are not correctly applied to Fragment contents (only to Activity)FragmentContainerView
, NavController
fragment can't be retrieved by
using findNavController()
(ISSUE-142847973,
STACKOVERFLOW-59275182)continuation
parameter in the AlbumListViewModelTest
class (Issue-957), , so test
in the AlbumDetailViewModelTest
was disabledANDROID_TEST_USES_UNIFIED_TEST_PLATFORM
yet.FileName
rule has to be disabled, because it is not compatible with fie contain a single extension
ISSUE-1657androidx.compose.runtime.getValue
and androidx.compose.runtime.setValue
imports are can't be resolved
automatically - they had to be added manually KTIJ-23200AndroidManifest.xml
file (KT-27971)ArchTaskExecutor
(Issue 79189568)buildFeatures
and testOptions
blocks are incubating and have to be marked as @Suppress ("UnstableApiUsage")
This project is being maintained to stay up to date with leading industry standards. Please check the CONTRIBUTING page if you want to help.
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
: