Blog Infos
Author
Published
Topics
Published

Today we will figure out how to manage your build scripts more efficiently, avoid boilerplate and code duplication. We will cover two major topics: dependencies management and common configuration control.

This article was inspired by the Now in Android app. You will notice during reading this article that many parts of our examples will appear very similar. We will follow the main idea and add some additional comments and explanations. I hope you enjoy the article, so let’s begin.

Create a new project

We will start by creating a new project and analyzing what we have in our build files. The picture can be slightly different, but you have probably already seen something like the following code snippet in your application build file. The plugins section, which contains information about using plugins, is nothing special, just a compact part:

plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
}
view raw build.gradle hosted with ❤ by GitHub

/composite-build-example/app/build.gradle

The android section contains noticeably more information than the plugins section:

android {
namespace 'com.touchlane.compositebuild.example'
compileSdk 33
defaultConfig {
applicationId "com.touchlane.compositebuild.example"
minSdk 24
targetSdk 33
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary true
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
buildFeatures {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion '1.2.0'
}
packagingOptions {
resources {
excludes += '/META-INF/{AL2.0,LGPL2.1}'
}
}
}
view raw build.gradle hosted with ❤ by GitHub
/composite-build-example/app/build.gradle

The dependencies section is the most changeable. In the starter project, it is somewhere between the plugins and android parts:

dependencies {
implementation 'androidx.core:core-ktx:1.9.0'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1'
implementation 'androidx.activity:activity-compose:1.6.1'
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
implementation 'androidx.compose.material3:material3:1.0.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version"
}
view raw build.gradle hosted with ❤ by GitHub

/composite-build-example/app/build.gradle

In general, there aren’t any problems with the build scripts above unless you decide to add a new module. What problems will we face when we add a new module? We should have a very close android section, as well as a similar dependencies section. Sure, dependencies wouldn’t be the same, but usually, we will have a pretty extensive intersection of dependencies in our project modules. But size in the dependencies section isn’t a problem, whereas the problem is managing library versions across all modules.

In cases when you have more than one module, it is highly recommended to use a version catalog.

Version catalog

A version catalog is a list of dependencies, represented as dependency coordinates, that a user can pick from when declaring dependencies in a build script.

Using of version catalog gives us a few very important advantages:

  • Type-safe accessors so that we can easily add dependencies with autocompletion in the IDE.
  • It is a central place to declare a version of a dependency and to make sure that a change to that version applies to every subproject.
  • Catalogs can declare dependency bundles, which are “groups of dependencies” that are commonly used together.
  • Catalogs can separate the group and name of a dependency from its actual version and use version references instead, making it possible to share a version declaration between multiple dependencies.

We will not go into detail in this section because there are a lot of very detailed articles, such as this one. Instead, we will briefly cover the most relevant steps.

So, let’s add a version catalog to our project. First of all, we need to create a TOML file under the gradle directory and fill it with the required dependencies:

If a libs.versions.toml file is found in the gradle subdirectory of the root build, then a catalog will be automatically declared with the contents of this file.

If you want to place it in another place or name it differently, follow the official instructions.

The structure of TOML file is very simple and consists of 4 major sections:

  • the [versions] section is used to declare versions which can be referenced by dependencies
  • the [libraries] section is used to declare the aliases to coordinates
  • the [bundles] section is used to declare dependency bundles
  • the [plugins] section is used to declare plugins
[versions]
androidGradlePlugin = "7.3.1"
androidxActivity = "1.6.1"
androidxComposeBom = "2022.10.00"
kotlin = "1.7.20"
[libraries]
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "androidxActivity" }
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "androidxComposeBom" }
androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" }
androidx-compose-ui = { group = "androidx.compose.ui", name = "ui" }
[bundles]
compose = ["androidx-activity-compose", "androidx-compose-material3", "androidx-compose-ui"]
[plugins]
android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" }
android-library = { id = "com.android.library", version.ref = "androidGradlePlugin" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }

/composite-build-example/gradle/libs.versions.toml

In addition, to take advantage of all the features of autocompletion, we will migrate all our build script files to Gradle Kotlin DSL.

After changing our app module dependencies section will look as follows:

dependencies {
val composeBom = platform(libs.androidx.compose.bom)
implementation(composeBom)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.ui)
implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.androidx.core.ktx)
testImplementation(libs.junit4)
androidTestImplementation(composeBom)
androidTestImplementation(libs.androidx.test.ext)
androidTestImplementation(libs.androidx.test.espresso.core)
androidTestImplementation(libs.androidx.compose.ui.test)
debugImplementation(libs.androidx.compose.ui.tooling)
debugImplementation(libs.androidx.compose.ui.testManifest)
}

/composite-build-example/app/build.gradle.kts

Now the dependencies section looks much better. If we add a new module, for instance :core:ui, we wouldn’t need to control library versions separately. All that we need to do is update the versions in our libs.versions.toml file.

When we will start using TOML files, Android Studio stopped showing us expired dependencies. The solution here is to start using one of the tools to control dependencies updates such as Renovate or Version catalog update plugin.

Composite build

Let’s go back to our android section and analyze what we have in it.

android {
namespace 'com.touchlane.compositebuild.example'
compileSdk 33
defaultConfig {
applicationId "com.touchlane.compositebuild.example"
minSdk 24
targetSdk 33
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary true
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
buildFeatures {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion '1.2.0'
}
packagingOptions {
resources {
excludes += '/META-INF/{AL2.0,LGPL2.1}'
}
}
}
view raw build.gradle hosted with ❤ by GitHub

/composite-build-example/app/build.gradle.kts

n general, we can group all sections by two purposes: android-specific sections and 3-rd party sections. The android-specific sections can be divided into two categories — library and application. For instance, we can refer to 3-rd party specific sections Kotlin related content. The majority of the sections from the app-level android section will be copied into your newly created modules. The same as with the dependencies section you have to control all similar sections across the entire project.

One of the most effective solutions is to start using a composite build approach.

A composite build is simply a build that includes other builds. In many ways a composite build is similar to a Gradle multi-project build, except that instead of including single projects, complete builds are included.

Why not a buildSrc? The answer is simple and based on official documentation:

A change in buildSrc causes the whole project to become out-of-date.

The Gradle team works on this problem and has the plan to make buildSrc an included build, but now it isn’t.

First of all, we need to create a new project inside our main project. Let’s name it build-logic:

The core structure should contain settings.gragle.kts file:

dependencyResolutionManagement {
repositories {
google()
mavenCentral()
}
versionCatalogs {
create("libs") {
from(files("../gradle/libs.versions.toml"))
}
}
}
rootProject.name = "build-logic"
include(":convention")

/composite-build-example/build-logic/settings.gradle.kts

In our project, we will use the same TOML file as in our main project. To avoid duplication we can specify a direct path to the TOML file and use it. Additionally, we need a module to put all our logic. Let’s call it a convention.

plugins {
`kotlin-dsl`
}
group = "com.touchlane.compositebuild.example.buildlogic"
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
dependencies {
compileOnly(libs.android.gradlePlugin)
compileOnly(libs.kotlin.gradlePlugin)
}
gradlePlugin {
plugins {
//Here is the place to register plugins
}
}

/composite-build-example/build-logic/convention/build.gradle.kts

The build.gradle.kts looks pretty simple with minimal required parameters. We are interested in the plugins section under the root gradlePlugin section. It’s the place where we register our custom plugins. The main idea is to move common parts from application build.gradle.kts to a specific plugin. This approach helps us achieve a single source of truth for our modules.

Following the single responsibility principle, we will split logic into very small atomic groups that will cover specific cases.

Job Offers

Job Offers


    Android Software Engineer (f/m/d)

    Paradox Cat GmbH
    Munich
    • Full Time
    apply now

    Senior Android Software Engineer (f/m/d)

    Paradox Cat GmbH
    Munich
    • Full Time
    apply now

    Kotlin Multiplatform Mobile Developer

    Touchlab
    Remote
    • Full Time
    apply now
Load more listings

OUR VIDEO RECOMMENDATION

, ,

From Scoped Storage to Photo Picker: Everything to know about Storage

Persistence is a core element of every mobile app. Android provides different APIs to access or expose files with different tradeoffs.
Watch Video

From Scoped Storage to Photo Picker: Everything to know about Storage

Yacine Rezgui
Android developer advocate
Google

From Scoped Storage to Photo Picker: Everything to know about Storage

Yacine Rezgui
Android developer ad ...
Google

From Scoped Storage to Photo Picker: Everything to know about Storage

Yacine Rezgui
Android developer advocat ...
Google

Jobs

Android application plugin

The first plugin will help us reduce code related to android application logic e.g. minSdkcompileSdk, etc. Firstly, we need to create a plugin class:

class AndroidApplicationConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
with(pluginManager) {
apply("com.android.application")
}
extensions.configure<ApplicationExtension> {
configureAndroidCommon(this)
configureAndroidApplication(this)
}
}
}
}

/composite-build-example/build-logic/convention/src/main/kotlin/…

So, let’s take a look at what we have. We have the plugin that applied to the project. In the apply section, we have 2 subsections. The first one is:

with(pluginManager) {
apply("com.android.application")
}

/composite-build-example/build-logic/convention/src/main/kotlin/…

The section above applies to the Android application plugin. Remember this trick because later, in our app’s build.gradle.kts file we’ll replace the Android application plugin with our customized AndroidApplicationConventionPlugin. The second section is:

extensions.configure<ApplicationExtension> {
configureAndroidCommon(this)
configureAndroidApplication(this)
}

/composite-build-example/build-logic/convention/src/main/kotlin/…

Notice that here we have a deal with ApplicationExtension. This means that this plugin will work only in an Android application module. In other words, we have to use this plugin only in combination with the com.android.application plugin. You might notice that we have 2 very similar methods: configureAndroidCommon and configureAndroidApplication. The reason is that ApplicationExtension is compatible only with com.android.application, but we also need to manage the com.android.library plugin. In cases where we deal with com.android.library we will make a deal with LibraryExtension. The fact is that ApplicationExtension and LibraryExtension have slightly different interfaces but they both have a common parent — CommonExtension that contains a lot of fields but not all that we need.

For that reason, we split the codebase into 2 methods (actually 3 if we consider working with the com.android.library plugin), configureAndroidCommon:

internal fun Project.configureAndroidCommon(
extension: CommonExtension<*, *, *, *>,
) {
extension.apply {
compileSdk = 33
defaultConfig {
minSdk = 24
vectorDrawables {
useSupportLibrary = true
}
}
buildFeatures {
compose = true
aidl = false
buildConfig = false
renderScript = false
shaders = false
}
packagingOptions {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
}
}

/composite-build-example/build-logic/convention/src/main/kotlin/…

As you can see we moved a lot of stuff here. That means that you don’t need to have this code in your newly built and existing modules. Only targetSdk is not available to configure our app, and the solution is the configureAndroidApplication method:

internal fun Project.configureAndroidApplication(
extension: ApplicationExtension,
) {
extension.apply {
defaultConfig {
targetSdk = 33
}
}
}
/composite-build-example/build-logic/convention/src/main/kotlin/…

The next step that we need to do is register our plugin in the build-logic plugin. To do that we need to do the following:

gradlePlugin {
plugins {
register("androidApplication") {
id = "compositebuild.android.application"
implementationClass = "AndroidApplicationConventionPlugin"
}
}
}

/composite-build-example/build-logic/convention/build.gradle.kts

Finally, we need to include our build-logic project in the app build pipeline. To do that we need to add the following code to the application-level settings.gradle.kts file:

pluginManagement {
includeBuild("build-logic")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
rootProject.name = "Composite Build Example"
include(":app")
include(":core")
include(":core:ui")

/composite-build-example/settings.gradle.kts

Here will be the most interesting part — we’ll replace blocks in our application build.gradle.kts file with our plugin and check how many lines of code we reduce.

//Before
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
}
//After
plugins {
id("compositebuild.android.application")
alias(libs.plugins.kotlin.android)
}

/composite-build-example/app/build.gradle.kts

And the impressive part, we can reduce the android section to these lines:

android {
namespace = "com.touchlane.compositebuild.example"
defaultConfig {
applicationId = "com.touchlane.compositebuild.example"
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
getByName("release") {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
composeOptions {
kotlinCompilerExtensionVersion = libs.versions.androidxComposeCompiler.get()
}
}

/composite-build-example/app/build.gradle.kts

Kotlin plugin

Let’s define another plugin and move the Kotlin setup into it. We’ll start with creating a plugin class:

class AndroidKotlinConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
with(pluginManager) {
apply("org.jetbrains.kotlin.android")
}
extensions.findByType(ApplicationExtension::class.java)?.apply {
configureKotlin(this)
}
extensions.findByType(LibraryExtension::class.java)?.apply {
configureKotlin(this)
}
}
}
}
/composite-build-example/build-logic/convention/src/main/kotlin/…

Here you can see that it looks slightly different from our AndroidApplicationConventionPlugin plugin. As we noticed before, the extension type depends on the type of plugin you use. For android applications we use the com.android.application plugin and we have access to ApplicationExtension. On the other hand in our modules, we use com.android.library and in this case, we have access to LibraryExtension. For that reason, we need to handle each case because highly likely we will use Kotlin in the whole project including modules.

Move all related logic into the configureKotlin function:

internal fun Project.configureKotlin(
commonExtension: CommonExtension<*, *, *, *>,
) {
commonExtension.apply {
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
}
}
fun CommonExtension<*, *, *, *>.kotlinOptions(block: KotlinJvmOptions.() -> Unit) {
(this as ExtensionAware).extensions.configure("kotlinOptions", block)
}
view raw Kotlin.kt hosted with ❤ by GitHub
/composite-build-example/build-logic/convention/src/main/kotlin/…

Register our plugin in our build-logic project:

gradlePlugin {
plugins {
register("androidApplication") {
id = "compositebuild.android.application"
implementationClass = "AndroidApplicationConventionPlugin"
}
register("androidKotlin") {
id = "compositebuild.jetbrains.kotlin.android"
implementationClass = "AndroidKotlinConventionPlugin"
}
}
}
/composite-build-example/build-logic/convention/build.gradle.kts

And finally, replace all related code in our application build.gradle.kts file:

//Before
plugins {
id("compositebuild.android.application")
alias(libs.plugins.kotlin.android)
}
//After
plugins {
id("compositebuild.android.application")
id("compositebuild.jetbrains.kotlin.android")
}
/composite-build-example/app/build.gradle.kts

The android section will look like this:

android {
namespace = "com.touchlane.compositebuild.example"
defaultConfig {
applicationId = "com.touchlane.compositebuild.example"
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
getByName("release") {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
composeOptions {
kotlinCompilerExtensionVersion = libs.versions.androidxComposeCompiler.get()
}
}

/composite-build-example/app/build.gradle.kts

Just compare it with our initial version. We reduced from around 47 lines of code in our android section to around 25! In addition, as a bonus, we create a single source of truth for our project.

Android library plugin

Now let’s create a module and compare the initial and final versions of the build.gradle.kts file. Here is the initial build.gradle.kts without dependencies:

plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.kotlin.android)
}
android {
namespace = "com.touchlane.compositebuild.core.ui"
compileSdk = 33
defaultConfig {
minSdk = 24
targetSdk = 33
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
getByName("release") {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
buildFeatures {
compose = true
aidl = false
buildConfig = false
renderScript = false
shaders = false
}
composeOptions {
kotlinCompilerExtensionVersion = libs.versions.androidxComposeCompiler.get()
}
}

/composite-build-example/core/ui/build.gradle.kts

You can notice that we don’t have com.android.library plugin implementation yet, so let’s fix that. First of all, we create a plugin class:

class AndroidLibraryConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
with(pluginManager) {
apply("com.android.library")
}
extensions.configure<LibraryExtension> {
configureAndroidCommon(this)
configureAndroidLibrary(this)
}
}
}
}
/composite-build-example/build-logic/convention/src/main/kotlin/…

Here we use the configureAndroidCommon method that was described above and additionally we use the configureAndroidLibrary method:

internal fun Project.configureAndroidLibrary(
extension: LibraryExtension,
) {
extension.apply {
defaultConfig {
targetSdk = 33
}
}
}

/composite-build-example/build-logic/convention/src/main/kotlin/…

You can say that the code above is the same as in the configureAndroidApplication function and you’ll be right. Both the ApplicationExtension and LibraryExtension APIs have the targetSdk field in defaultConfig. However, defaultConfig is represented by different classes: ApplicationDefaultConfig for ApplicationExtension and LibraryDefaultConfig for LibraryExtension and they do not have targetSdk property in their common parent DefaultConfig, so we have to separate our logic between the library and application extensions.

Don’t forget to add our plugin to the build-logic project:

gradlePlugin {
plugins {
register("androidApplication") {
id = "compositebuild.android.application"
implementationClass = "AndroidApplicationConventionPlugin"
}
register("androidKotlin") {
id = "compositebuild.jetbrains.kotlin.android"
implementationClass = "AndroidKotlinConventionPlugin"
}
register("androidLibrary") {
id = "compositebuild.android.library"
implementationClass = "AndroidLibraryConventionPlugin"
}
}
}
composite-build-example/build-logic/convention/build.gradle.kts

And let’s replace the content in our module build.gradle.kts:

plugins {
id("compositebuild.android.library")
id("compositebuild.jetbrains.kotlin.android")
}
android {
namespace = "com.touchlane.compositebuild.core.ui"
defaultConfig {
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
getByName("release") {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
composeOptions {
kotlinCompilerExtensionVersion = libs.versions.androidxComposeCompiler.get()
}
}
/composite-build-example/core/ui/build.gradle.kts

As you can see we have reduced the code in this part a lot.

Jetpack compose plugin

As we mentioned at the very beginning of this article you can move anything to your plugin if it helps you reduce boilerplate and improve maintenance. For instance, we can also move parts related to compose:

class AndroidComposeConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
extensions.findByType(ApplicationExtension::class.java)?.apply {
configureAndroidCompose(this)
}
extensions.findByType(LibraryExtension::class.java)?.apply {
configureAndroidCompose(this)
}
}
}
}

/composite-build-example/build-logic/convention/src/main/kotlin/…

Again, configureAndroidCompose is applied to 2 types of extensions: ApplicationExtension and LibraryExtension. It is also possible to create 2 separate plugins for ApplicationExtension and LibraryExtension e.g. AndroidApplicationComposeConventionPlugin and AndroidLibraryComposeConventionPlugin but in our example, we use the approach described above. And here is our configureAndroidCompose method:

internal fun Project.configureAndroidCompose(
commonExtension: CommonExtension<*, *, *, *>,
) {
val libs = extensions.getByType<VersionCatalogsExtension>().named("libs")
commonExtension.apply {
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion =
libs.findVersion("androidxComposeCompiler").get().toString()
}
dependencies {
val bom = libs.findLibrary("androidx-compose-bom").get()
add("implementation", platform(bom))
add("androidTestImplementation", platform(bom))
}
}
}

/composite-build-example/build-logic/convention/src/main/kotlin/…

Pay attention that we can also add some dependencies. As we move compose logic to a plugin, it makes sense to add dependencies to the BOM of compose to reduce dependencies in the application’s library section. Register our plugin in the build-logic project:

gradlePlugin {
plugins {
register("androidApplication") {
id = "compositebuild.android.application"
implementationClass = "AndroidApplicationConventionPlugin"
}
register("androidKotlin") {
id = "compositebuild.jetbrains.kotlin.android"
implementationClass = "AndroidKotlinConventionPlugin"
}
register("androidLibrary") {
id = "compositebuild.android.library"
implementationClass = "AndroidLibraryConventionPlugin"
}
register("androidCompose") {
id = "compositebuild.android.compose"
implementationClass = "AndroidComposeConventionPlugin"
}
}
}
/composite-build-example/build-logic/convention/build.gradle.kts

And replace the compose-related part in our application:

plugins {
id("compositebuild.android.application")
id("compositebuild.jetbrains.kotlin.android")
id("compositebuild.android.compose")
}
android {
namespace = "com.touchlane.compositebuild.example"
defaultConfig {
applicationId = "com.touchlane.compositebuild.example"
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
getByName("release") {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
}

/composite-build-example/app/build.gradle.kts

Additionally, we are reducing our dependencies part:

dependencies {
//No more need
//val composeBom = platform(libs.androidx.compose.bom)
//implementation(composeBom)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.ui)
implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.androidx.core.ktx)
testImplementation(libs.junit4)
//No more need
//androidTestImplementation(composeBom)
androidTestImplementation(libs.androidx.test.ext)
androidTestImplementation(libs.androidx.test.espresso.core)
androidTestImplementation(libs.androidx.compose.ui.test)
debugImplementation(libs.androidx.compose.ui.tooling)
debugImplementation(libs.androidx.compose.ui.testManifest)
}

/composite-build-example/app/build.gradle.kts

Congratulations, now you know how to make your work with build files more productive and maintainable. You can always add your custom logic to plugins to reduce the repeatable work that you do when creating a new module. As a developer, you and only you decide what part of the code goes to the plugin, and here are a few really useful examples for you to explore.

This article was originally published on proandroiddev.com

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
It’s one of the common UX across apps to provide swipe to dismiss so…
READ MORE
blog
In this part of our series on introducing Jetpack Compose into an existing project,…
READ MORE
blog
This is the second article in an article series that will discuss the dependency…
READ MORE
blog
Let’s suppose that for some reason we are interested in doing some tests with…
READ MORE

Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.

Menu