An Android template application showcasing: Multi modular clean architecture, reactive patterns, dependency injection, integration with jetpack libraries, testing and CI/CD.
We’re always looking for people who value their work, so come and join us. We are hiring!
- Open the project in Android Studio and sync dependencies with gradle.
- Run the app by pressing the run button in android studio or by pressing
control + R
. - Go through and setup the scripts in the scripts directory.
Sensitive information such as the api keys is managed via local.properties
file. This file in not checked into version control to keep the sensitive information safe. If you want to run the project locally you need to add your own api keys.
Look at the local.skeleton.properties
file for all the keys you need to include in your local.properties
file.
You can get the Open Weather API key from openweathermap.org.
The architecture of the template facilitates separation of concerns and avoids tight coupling between it's various layers. The goal is to have the ability to make changes to individual layers without affecting the entire app. This architecture is an adaptation of concepts from The Clean Architecture
.
The architecture is separated into the following layers
presentation
: All UI and state management elements like Activities / Fragments (soon composables), View Models, Components, etc.navigation
: navigators to navigate between Screens.interactor
: provides feature specific functionality and handle coroutine contexts.domain
: use cases for individual pieces of work.repository
: repositories to manage various data sources.service
: services provide access to external elements such as databases, apis, etc.
Each layer has 3 modules:
- A module for the interfaces. These modules have no suffix. For example: service
- A module for the implementation. These modules are suffixed with
-impl
. For example: service-impl - A module for Dependency Injection. These modules are suffixed with
-di
. For example: service-di
The layers presentation
, domain
and services
each have an entity
module as well.
presentation-entity
: Data Classes that model the visual elements used by the widgets.domain-entity
: Data classes for performing business logic manipulations. They act as a abstraction to hide the local and remote data models.service-entity
: Contains local models (data classes for the database) and remote models (data classes for the api).
- Presentation entities are prefixed with
UI
(eg: UICity). - Domain entities do not have any prefix. (eg: City).
- Service entities are of 2 types:
- Local / Database entities are prefixed with
Local
(eg: LocalCity). - Remote / API entities are prefixed with
Remote
(eg: RemoteCity).
- Local / Database entities are prefixed with
There is a pattern in which all these modules depend on each other.
- The
interface
module in a layer is just the name of the layer.
Example:
repo-interface
is justrepo
- The
implementation
module in a layer is just the name of the layer, suffixed by-impl
.
Example:
repo-implementation
is justrepo-impl
- The
implementation
modules depend on theinterface
modules of the same layer and the layer directly below it.
- The
interface
modules may depend on theinterface
modules of the layer below.
- The
di
modules depend on theinterface
andimplementation
modules of the same layer. And may also depend on theinterface
module of the layer below.
Apart from these, the layer that have entity modules depend on entity module of the same layer. The layers that don't have entity modules depend on the entity modules of the layer above and below.
- All the dependencies are listed in the
buildSrc
module in theDependencies.kt
file.. - Android related setup like build types and flavors is in
android.gradle
which is imported in each module. - Linting setup is done in
lint.gradle
which is imported in each module. - The
build.gradle.kts
files for each module are renamed tomodule-name.gradle.kts
so that it is easy to locate them. For example, the gradle file forservice
module is calledservice.gradle.kts
.
The presentation layer houses all the visual components and state management logic.
The base
directory has all the reusable and common elements used as building blocks for the UI like common components, base classes, extensions, etc.
Each View Model
is a sub class of the BaseViewModel
. The BaseViewModel
. View Models also have the SavedStateHandle
injected into them.
View Model exposes a LiveData of ScreenState
from the SavedStateHandle. Along with the ScreenState it also exposes a LiveData of Effect
.
Implementations of the BaseViewModel can also choose to handle Intents
.
ScreenState
encapsulates all the state required by a Fragment. State is any data that represents the current situation of a Page.
For example, the HomeScreenState
holds the state required by the HomeFragment
.
Effects
are events that take place on a fragment that are not part of the state of the screen. These usually deal with UI elements that are not part of the xml layout.
Showing a snackbar or hiding the keyboard are examples of an effect.
Intent is any action takes place on fragment. It may or may not be user initiated.
SearchScreenIntent
has the actions that can happen on the SearchFragment
.
Components are reusable parts of UI. Components can be used in any fragment using the by composable
delegate provided by the BaseFragment
.
Components can be anything from a simple component to hide and show the loading indicator to a complex component like ListComponent
that can manage normal and nested recycler view efficiently.
Each Fragment must extend the BaseFragment
.
The BaseFragment
provides the ViewModel
with the navigator and the Screen
. It listens to the screen state and effect live data from the view model and notifies the fragment about it. It also binds and unbinds all the components in the appropriate lifecycle callbacks.
Each Fragment may receive the Screen
as arguments when navigating.
A Screen
is a class that represents a Fragment
in the context of navigation. It holds the path or id
used by the navigator to navigate to a Fragment
and also holds any arguments required to navigate to that Fragment
.
The template comes with built-in support for 3 flavors
- Dev - This flavour is used across the development stage
- QA - This flavour would be used when the app is under review by the QA Team or the Project Manager
- Prod - This flavour would be used when the app is ready to be deployed to a store or shipped to the user
Note: Flavour specific configurations can be made in the app.gradle file under the
buildTypes
block
The Android Template contains:
- An
Android 12
application. - Built-in support for 3
flavors
-dev
,qa
andprod
. - A
reactive base architecture
for your application. Room
as local persistent database.Retrofit
for api calls.Kotlinx serialization
for json conversion.Koin
for dependency injection.Timber
for logging.Chucker
for on device api call logging.Ktlint
for linting the codebase.
The Android template comes with built in support for CI/CD using Github Actions.
The CI
workflow performs the following checks on every pull request:
- Lints the code with
./gradlew lintRelease
. (As an additional process, we also run./gradlew ktlint
which is based on the Ktlint Kotlin linter) - Runs tests using
./gradlew testDebugUnitTest
. - Build the android app as the
dev
flavor to check if the building process works.
The CD
workflow performs the following actions:
- Bump the
versionCode
by 1. For details read Version your app - Build a release apk (
prod
flavor). - Sign the apk using
apksigner
- Upload apk to app center.
- Upload apk as artifact to release tag.
- Commit the updated version to git.
Note
: It is recommended to keep your keystore and its essentials like: alias, password safe and encrypted, inside your Github Secrets
For the android CD workflow to run, we need to perform the following setup steps:
- Follow these instructions to generate an upload keystore. Note down the
store password
,key alias
andkey password
. You will need these in later steps. - Use
openssl
to convert thejks
file toBase64
.
openssl base64 < android_template_keystore.jks | tr -d '\n' | tee android_template_keystore_encoded.txt
- Store the
base64
output onGithub Secrets
with the key nameKEYSTORE
. - Save the
store password
in github secrets with key nameKEYSTORE_PASSWORD
. - Save the
key alias
in github secrets with key nameKEY_ALIAS
. - Save the
key password
in github secrets with key nameKEY_PASSWORD
. - Create a distribution on app center and get the upload key. You can get it from from Settings.
- Save the app center upload key on github secrets with key name
APP_CENTER_USER_API_TOKEN
. - Save the group name as
GROUP_NAME
inside github secrets. Example:Testers
- Save the organisation name as
ORG_NAME
inside github secrets. Example:Wednesday Solutions
- Save the app name as
APP_NAME
inside github secrets. Example:Android Template
Caution
: Respect the of the value inside secrets or else AppCenter APIs might have problems looking for your app