A collection of Gradle convention plugins for Android projects, designed to keep a single source of truth for common module configurations.
This approach is based on:
Convention plugins avoid duplicated build script setup and messy subprojects blocks, without the pitfalls of buildSrc. They are additive and composable — each plugin has a single responsibility, and modules pick only what they need. One-off logic that isn't shared should stay directly in the module's build.gradle.kts.
- Why convention plugins?
- Requirements
- Structure
- Setup
- Plugins
- Usage by module type
- What each plugin configures
- Dependency bundle helpers
- Tasks
- Signing
- Updating the submodule
| Approach | Drawback |
|---|---|
| Copy/paste build config | Drifts out of sync across modules |
subprojects {} / allprojects {} |
Forces config on modules that don't need it, hurts caching |
buildSrc |
Invalidates the whole build cache on any change |
| Convention plugins ✅ | Opt-in, composable, cache-friendly, single responsibility |
| Tool | Version |
|---|---|
| JDK | 17 |
| Min SDK | 24 |
| Gradle | 8.x+ (uses includeBuild / composite builds) |
| AGP / Kotlin | Provided by the host project's version catalog |
All SDK, JDK and toolchain versions are read from the host project's
gradle/libraries.versions.toml. Seemodel/Versions.ktfor the keys consumed by the plugins.
android-convention/
├── convention/ # Convention plugins module
│ ├── build.gradle.kts # Plugin classpath (AGP, Kotlin, KSP, coverage…)
│ └── src/main/kotlin/
│ ├── android-application-conventions.gradle.kts
│ ├── android-library-conventions.gradle.kts
│ ├── android-feature-conventions.gradle.kts
│ ├── android-compose-application-conventions.gradle.kts
│ ├── android-compose-library-conventions.gradle.kts
│ ├── android-compose-feature-conventions.gradle.kts
│ ├── android-project-conventions.gradle.kts
│ ├── extension/ # Shared Kotlin helpers
│ │ ├── CommonExtension.kt # android {} accessor helpers
│ │ ├── DependencyExtension.kt # *Bundle dependency helpers
│ │ ├── ProjectExtension.kt # getProperty / signing props
│ │ ├── URLExtension.kt # repo download helper
│ │ └── VersionCatalogExtensions.kt # libs / versions accessors
│ ├── model/
│ │ └── Versions.kt # Type-safe version-catalog keys
│ └── task/
│ ├── DownloadGradleDependencies.kt # Refresh build-logic from remote
│ └── DownloadGithubActions.kt # Sync shared GitHub Actions
├── gradle/
│ └── libraries.versions.toml # Fallback version catalog
└── settings.gradle.kts
From your project's root directory:
git submodule add [email protected]:raxden/android-convention.git build-logicpluginManagement {
includeBuild("build-logic")
repositories {
google {
content {
includeGroupByRegex("com\\.android.*")
includeGroupByRegex("com\\.google.*")
includeGroupByRegex("androidx.*")
}
}
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
versionCatalogs {
create("libs") {
from(files("./gradle/libraries.versions.toml"))
}
}
}
build-logic/settings.gradle.ktsresolves the version catalog automatically: it first looks for../gradle/libraries.versions.toml(i.e. the host project's catalog), and falls back to its owngradle/libraries.versions.toml. This means the host project'sgradle/catalog is the source of truth andbuild-logicpicks it up without any extra configuration.
plugins {
alias(libs.plugins.android.project.conventions)
// other plugins with `apply false` ...
}| Plugin | Alias | Use for |
|---|---|---|
android-application-conventions |
libs.plugins.android.application.conventions |
:app module (no Compose) |
android-compose-application-conventions |
libs.plugins.android.compose.application.conventions |
:app module with Compose |
android-library-conventions |
libs.plugins.android.library.conventions |
Standalone library modules |
android-feature-conventions |
libs.plugins.android.feature.conventions |
Feature modules (no Compose) |
android-compose-library-conventions |
libs.plugins.android.compose.library.conventions |
Library modules with Compose |
android-compose-feature-conventions |
libs.plugins.android.compose.feature.conventions |
Feature modules with Compose |
android-project-conventions |
libs.plugins.android.project.conventions |
Root project only |
android-application-conventions
└── android-compose-application-conventions
android-library-conventions
├── android-feature-conventions
└── android-compose-library-conventions
└── android-compose-feature-conventions
The most common case. Applies signing, build types, Compose, and KotlinX Serialization.
// app/build.gradle.kts
plugins {
alias(libs.plugins.android.compose.application.conventions)
}
android {
namespace = "com.example.myapp"
defaultConfig {
applicationId = "com.example.myapp"
}
}For modules that provide utilities, networking, data, etc. without a UI layer.
// core/network/build.gradle.kts
plugins {
alias(libs.plugins.android.library.conventions)
}
android {
namespace = "com.example.app.core.network"
}For feature and data modules that include Compose UI or depend on Compose infrastructure.
// feature/home/build.gradle.kts
plugins {
alias(libs.plugins.android.compose.feature.conventions)
}
android {
namespace = "com.example.app.feature.home"
}Configures coverage reports and project-report across all subprojects.
// build.gradle.kts
plugins {
alias(libs.plugins.android.project.conventions)
alias(libs.plugins.android.application) apply false
alias(libs.plugins.android.library) apply false
// ...
}- Applies:
com.android.application,com.google.devtools.ksp,kotlin-parcelize compileSdk,minSdk,targetSdkfrom version catalogbuildFeatures.buildConfig = truetestInstrumentationRunner = androidx.test.runner.AndroidJUnitRunnerdebugsigning config usingconfig/debug.keystore(aliasandroiddebugkey)releasesigning config loaded fromconfig/signing.release.propertiesdebug:applicationIdSuffix = ".debug", minify disabled, unit + Android test coverage enabledrelease: minify + resource shrinking enabled, proguard configured- Packaging excludes for common
META-INFlicense/kotlin-module clashes - Java/Kotlin toolchain (
jvmToolchain) set from version catalog
Extends android-application-conventions and adds:
org.jetbrains.kotlin.plugin.composeorg.jetbrains.kotlin.plugin.serializationbuildFeatures.compose = truekotlinx-serialization-jsondependency
- Applies:
com.android.library,com.google.devtools.ksp,kotlin-parcelize compileSdk,minSdkfrom version catalogdebug: coverage enabled (enableUnitTestCoverage,enableAndroidTestCoverage)- Java/Kotlin toolchain set from version catalog
Thin wrapper — applies android-library-conventions. Use it to semantically distinguish feature modules from generic libraries.
Extends android-library-conventions and adds:
org.jetbrains.kotlin.plugin.composebuildFeatures.compose = true
Thin wrapper — applies android-compose-library-conventions. Use it for feature modules that include Compose UI.
Root-project only. Configures:
- Code coverage aggregation via
rootcoverage(HTML + XML reports, unit + Android test results), with sensibleexcludesfor DI, generated, Compose and Android-framework classes project-reportplugin on all subprojects- Registers the
downloadGradleDependenciesanddownloadGithubActionstasks
The build variant used by coverage is controlled by the
buildTypeGradle property (defaults todebug).
extension/DependencyExtension.kt provides helpers that expand a version-catalog bundle and route each dependency to the correct configuration automatically:
- Entries whose name contains
bomare added withplatform(...) - Entries whose name contains
compilerare added to the matching KSP configuration - Everything else is added to the standard configuration
| Helper | Configuration | KSP target |
|---|---|---|
implementationBundle(...) |
implementation |
ksp |
debugImplementationBundle(...) |
debugImplementation |
— |
androidTestImplementationBundle(...) |
androidTestImplementation |
kspAndroidTest |
import extension.implementationBundle
dependencies {
// A bundle that may mix a BOM, a compiler (KSP) and regular libraries —
// each entry is dispatched to the right configuration automatically.
implementationBundle(libs.bundles.room)
}Registered by android-project-conventions under the dependencies group:
| Task | Description |
|---|---|
downloadGradleDependencies |
Downloads the latest android-convention archive into build-logic/, refreshing the convention plugins. |
downloadGithubActions |
Downloads the shared android-github-actions repo and syncs its contents into the host project's .github/. |
./gradlew downloadGradleDependencies
./gradlew downloadGithubActionsCoverage report (from the
rootcoverageplugin):./gradlew rootCoverageReport
android-application-conventions expects the following files at the root of the host project:
| Build type | File |
|---|---|
debug |
config/debug.keystore (standard Android debug keystore) |
release |
config/signing.release.properties |
signing.release.properties format:
storeFile=config/release.keystore
storePassword=your_store_password
keyAlias=your_key_alias
keyPassword=your_key_passwordKeep
config/signing.release.propertiesandconfig/release.keystoreout of version control. Thedebugkeystore uses the well-known Android defaults and is safe to commit.
Since build-logic is a Git submodule, pull the latest convention plugins with:
git submodule update --remote build-logicOr, without managing the submodule manually, run the bundled task to re-download the latest version into build-logic/:
./gradlew downloadGradleDependenciesCopyright 2022 Ángel Gómez
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.