Blog Infos
Author
Published
Topics
, ,
Published

I recently finished migrating my Gradle scripts from Groovy to Kotlin DSL on my current project. It took me a lot of time searching and investigating different approaches and solutions, so I decided to collect everything in one article to simplify life for everyone else who will face a similar task in the future.

if you are from Ukraine β€” here isΒ UA versionΒ for you!

In this article, I will consider my case β€” migrating an existing project from Gradle 7+ to a newer version with Gradle Kotlin DSL, Android Kotlin Plugin, Version Catalog, and KSP. As a result, I ended up with this setup:

Android Studio:Β Narwhal 2025.1.2
Gradle:Β 8.11.1
Android Gradle Plugin (AGP):Β 8.10.1
Kotlin:Β 2.1.21
Compose:Β 2.1.21
KSP:Β 2.1.21–2.0.1

Everything is the newest, but already stable enough to be used in production. So, if you’re interested in how to set all this up β€” let’s dive in!

As always, a little theory first…

First of all, you need to start with the main thing β€” the IDE. If you want to work with the latest Gradle features, you need to update to the latest version of Android Studio. Therefore, I highly recommend checking theΒ tableΒ that shows the compatible versions of Studio and Gradle.

Personally, I useΒ Narwhal 2025.1.2Β andΒ Koala 2024.1.2Β in parallel to work with old and new projects.

If you already need to update, don’t forget to check which version of the Android SDK is supported by your Studio and AGP, so you don’t have to do it twice. You’ll have to update yourΒ target-sdkΒ once a year anyway, so my advice is to check everything in advance so that your Studio, Gradle, and AGP already support the latest SDK version.

The next step is, in fact, the Gradle version. Here is aΒ linkΒ to the AGP and Gradle compatibility table. Everything is simple here β€” choose the appropriate Gradle version to build your project and add it toΒ gradle/gradle-wrapper.properties:

distributionUrl=https\://services.gradle.org/distributions/gradle-[VERSION]-bin.zip

The last thing you need to choose correctly is the Kotlin version. To be honest, I couldn’t find a corresponding table for this, so I chose almost at random: I set a version and tried to compile. I was lucky, so I conclude thatΒ Kotlin 2.1.21Β works withΒ AGP 8.10.

If you, the reader, know where to find the correspondence between Kotlin and Gradle, I would be very grateful for a comment. And if you’re also annoyed that there’s still no online generator that correctly puts all this together and gives hints β€” hit a like!

So, we have the versions of all the necessary components: Android Studio, AGP, Gradle, Kotlin. What else do you need? The correct answer is β€” Compose!

We’re very lucky that, starting from version 2.0, Compose has become a plugin that has the same version as Kotlin, so we don’t need to look for correspondences, because the plugin will do it itself internally! And choosing the KSP version is the easiest thing of all! Here is the KSP version pattern:Β 2.1.21–2.0.1, where the first part is the compatible Kotlin version, and the second is the KSP version itself. You can find this in the releases onΒ Github.

Moving on to practice

My project has the following modules: an Android application, an Android library, and several connected binary libs, which are also separated into individual modules for ease of connection and configuration.

I recommend starting from the project root and configuring theΒ version catalogΒ first. It should be created inΒ gradle/libs.versions.toml.

As an example, here’s what I came up with:

[versions]
sdk-minimal = "26"
sdk-target = "34"
version-kotlin = "2.1.21"
version-ksp = "2.1.21-2.0.1"
version-agp = "8.10.1"
version-dagger = "2.55"

[libraries]
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version = "1.10.2" }
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version = "1.10.2" }
kotlinx-collections-immutable = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version = "0.4.0" }

hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "version-dagger" }
hilt-compiler = { module = "com.google.dagger:hilt-compiler", version.ref = "version-dagger" }

androidx-core-ktx = { module = "androidx.core:core-ktx", version = "1.10.1" }
androidx-appcompat = { module = "androidx.appcompat:appcompat", version = "1.7.1" }
androidx-activity-compose = { module = "androidx.activity:activity-compose", version = "1.10.1" }
androidx-hilt-compose = { module = "androidx.hilt:hilt-navigation-compose", version = "1.2.0" }

compose-bom = { module = "androidx.compose:compose-bom", version = "2025.05.01" }
compose-ui = { module = "androidx.compose.ui:ui"}
compose-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" }
compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling" }
compose-material = { module = "androidx.compose.material:material" }

lifecycle-extensions = { module = "androidx.lifecycle:lifecycle-extensions", version = "2.2.0" }
lifecycle-livedata = { module = "androidx.lifecycle:lifecycle-livedata-ktx", version = "2.6.1" }
lifecycle-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version = "2.6.1" }
lifecycle-runtime = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version = "2.6.1" }

room-runtime = { module = "androidx.room:room-runtime", version.ref = "version-room" }
room-compiler = { module = "androidx.room:room-compiler", version.ref = "version-room" }
room-ktx = { module = "androidx.room:room-ktx", version.ref = "version-room" }

firebase-bom = { module = "com.google.firebase:firebase-bom", version = "33.15.0" }
firebase-analytics = { module = "com.google.firebase:firebase-analytics-ktx" }
firebase-crashlytics = { module = "com.google.firebase:firebase-crashlytics-ktx" }
firebase-messaging = { module = "com.google.firebase:firebase-messaging-ktx" }

[plugins]
android-application-gradle-plugin = { id = "com.android.application", version.ref = "version-agp" }
android-library-gradle-plugin = { id = "com.android.library", version.ref = "version-agp" }
kotlin-android-gradle-plugin = { id = "org.jetbrains.kotlin.android", version.ref = "version-kotlin" }
kotlin-parcelize-gradle-plugin = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "version-kotlin" }
compose-compiler-gradle-plugin = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "version-kotlin" }
google-gms-services-gradle-plugin = { id = "com.google.gms.google-services", version = "4.3.15" }
crashlytics-gradle-plugin = { id = "com.google.firebase.crashlytics", version = "2.9.6" }
ksp-devtools-gradle-plugin = { id = "com.google.devtools.ksp", version.ref = "version-ksp" }
dagger-hilt-gradle-plugin = { id = "com.google.dagger.hilt.android", version.ref = "version-dagger" }

[bundles]
kotlinx = [
    "kotlinx-coroutines-core",
    "kotlinx-coroutines-android",
    "kotlinx-collections-immutable",
]
androidx = [
    "androidx-core-ktx",
    "androidx-appcompat",
    "androidx-activity-compose",
]
lifecycle = [
    "lifecycle-runtime-ktx",
    "lifecycle-viewmodel-ktx",
    "lifecycle-viewmodel-compose",
    "lifecycle-runtime-compose",
    "lifecycle-viewmodel-navigation3",
]

Here are just the basics, and you might have a lot more different libraries, but the point is this: separate your dependencies and keep them together in a special catalog.

The next step is to connect the catalog and configure theΒ plugin/dependency resolution strategy. If your version catalog has a standard name and path, it will be picked up automatically, so all you have to do is specify the repositories for plugins and libraries. As an example, I’ll show myΒ settings.gradle.ktsΒ configuration:

pluginManagement {
  repositories {
    /// By default, google maven repository has many different libs,
    /// plugins, SDKs and so on. We constraint it to take from gooogle
    /// only those artifacts, which contains android.*, google.*, androidx.*
    google {
      content {
        includeGroupByRegex("com\\.android.*")
        includeGroupByRegex("com\\.google.*")
        includeGroupByRegex("androidx.*")
      }
    }

    mavenCentral()
    gradlePluginPortal()
  }
}

dependencyResolutionManagement {
  /// My recomendation: set this config to include
  /// your modules with pre-generated accessors
  enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
  repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
  repositories {
    google()
    mavenCentral()
    maven(url = "https://jitpack.io")
  }
}

/// list of your modules. Generally added by Android Studio

Best practices recommend failing project synchronization if modules have custom repositories:Β RepositoriesMode.FAIL_ON_PROJECT_REPOS. But in practice, it’s not always so categorical πŸ™‚ I had to configure the build so that the repositories in the modules had priority over the repositories inΒ settings.gradle.kts.

The next step, I recommend configuringΒ build.gradle.kts, which is in the project root. In the simplest case, here we can add all those plugins from theΒ version catalog, so that the project “knows” about their existence. In more complex cases, you can add dependencies to theΒ classpathΒ (these are also plugins, but they are not in the general Gradle plugin portal) and configureΒ resolutionStrategyβ€”a strategy for managing identical libraries with different versions. Here is an example of myΒ build.gradle.kts:

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
  alias(libs.plugins.android.application.gradle.plugin) apply false
  alias(libs.plugins.android.library.gradle.plugin) apply false
  alias(libs.plugins.kotlin.android.gradle.plugin) apply false
  alias(libs.plugins.kotlin.parcelize.gradle.plugin) apply false
  alias(libs.plugins.crashlytics.gradle.plugin) apply false
  alias(libs.plugins.google.gms.services.gradle.plugin) apply false
  alias(libs.plugins.ksp.devtools.gradle.plugin) apply false
  alias(libs.plugins.compose.compiler.gradle.plugin) apply false
  alias(libs.plugins.dagger.hilt.gradle.plugin) apply false
}

buildscript {
  repositories {
    maven("https://aws-mobile-sdk.s3.amazonaws.com/android")
  }

  dependencies {
    classpath("com.amazonaws:aws-android-sdk-appsync-gradle-plugin:3.4.0")
  }
}

subprojects {
  tasks.withType<KotlinCompile>().configureEach {
    kotlinOptions {
      jvmTarget = JavaVersion.VERSION_17.toString()
      /// this is just an example of compiler args.
      /// Do not add them to your project, if you don't need them!
      freeCompilerArgs += listOf(
        "-Xcontext-receivers",
        "-opt-in=kotlin.RequiresOptIn",
        "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
        "-opt-in=kotlinx.coroutines.FlowPreview",
      )
    }
  }

  tasks.withType<Test>().configureEach {
    useJUnitPlatform()
    testLogging {
      events = setOf(
        TestLogEvent.PASSED,
        TestLogEvent.SKIPPED,
        TestLogEvent.FAILED,
      )
    }
  }

  configurations.all {
    resolutionStrategy {
        /// fix transitively added different versions of kotlin 
        /// with specific version we need.
        eachDependency {
          if (requested.group == "org.jetbrains.kotlin") {
            useVersion(libs.versions.version.kotlin.get())
          }
        }
      }

      /// ensure that we are using specific version of androidx-core,
      /// event if any legacy lib uses old one
      force("androidx.core:core-ktx:[our_specific_version]")
    }
  }
}

I had to pull one of the plugins through theΒ classpathΒ because it is only distributed from AWS repositories.

In addition to the Gradle scripts at the root, there is one more configuration file that is worth paying attention to:Β gradle.properties. I’ll briefly describe what is configured here:

  • Build parameter settings
  • Memory usage control
  • Enabling/disabling experimental features
  • Parameters for the Gradle Daemon
  • Passing parameters to plugins (Compose, KMP, AGP)

The simplest thing you can useΒ gradle.propertiesΒ for is to configure how much memory Gradle can use on your workstation. For example, for me it’s 16GB:

org.gradle.jvmargs=-Xmx16g -XX:MaxPermSize=16096m

That’s all for the project root configuration β€” let’s move on to the modules themselves!

I want to say right away that you can configure things here endlessly! But we’ll start in order and first look at what’s here in general:

android {
  namespace = "com.yourpackage.name"
  compileSdk = 35

  defaultConfig {
  }

  buildTypes {
  }

  productFlavors {
  }

  signingConfigs {
  }

  compileOptions {
  }

  kotlinOptions {
  }

  buildFeatures {
  }

  composeOptions {
  }
}

You can create kotlin extensions (after all, we have Kotlin DSL!), which will automatically configure everything we copy from module to module. For example,Β buildTypes:

val BuildTypes = buildList {
  this += BuildTypeData(
    type = BuildType.Debug,
    applicationIdSuffix = ".dev",
    resValues = buildList {
      this += ResValue(
        type = "string",
        name = "app_name",
        value = "Dev App Name",
      )
    },
    buildConfigFields = buildList {
      this += BuildConfigField(
        type = "String",
        name = "BASE_API_URL",
        value = "\"https://dev.api.example.com/\""
      )
    }
  )

  this += BuildTypeData(
    type = BuildType.Stage,
    applicationIdSuffix = ".stage",
    isAnalyticsEnabled = true,
    isCrashlyticsEnabled = true,
    resValues = buildList {
      this += ResValue(
        type = "string",
        name = "app_name",
        value = "Stage App Name",
      )
    },
    buildConfigFields = buildList {
      this += BuildConfigField(
        type = "String",
        name = "BASE_API_URL",
        value = "\"https://stage.api.example.com/\""
      )
    }
  )

  this += BuildTypeData(
    type = BuildType.Release,
    isProGuardEnabled = true,
    isMinifyEnabled = true,
    isShrinkResources = true,
    isAnalyticsEnabled = true,
    resValues = buildList {
      this += ResValue(
        type = "string",
        name = "app_name",
        value = "Release App Name",
      )
    },
    buildConfigFields = buildList {
      this += BuildConfigField(
        type = "String",
        name = "BASE_API_URL",
        value = "\"https://prod.api.example.com/\""
      )
    }
  )
}

In my case, I createdΒ BuildTypesβ€”a special container where all build types (debug, release, stage, etc.) are stored. And here’s how you can apply all this in practice inΒ app/build.gradle.kts:

inline fun <reified T> NamedDomainObjectContainer<T>.ensureBuildTypeBy(name: String, closure: T.() -> Unit = {}) {
    closure(findByName(name) ?: create(name))
}

buildTypes {
  BuildTypes.forEach {
    ensureBuildTypeBy(it.type.gradleName()).apply {
      versionNameSuffix = it.previewSuffix
      isDebuggable = (it.type == BuildType.Debug)
      it.resValues.forEach { (type, name, value) -> resValue(type, name, value) }
      it.buildConfigFields.forEach { (type, name, value) -> buildConfigField(type, name, value) }
    }
  }
}

In addition to build types, there are two other important things that can be configured in a similar way:Β flavorsΒ andΒ signingConfig. Everything is the same: you need to create a data model with the appropriate fields, fill them with data (forΒ signingConfig, this data can be taken from a localΒ .propertiesΒ file, but don’t forget to exclude it from the commit!) and iterate through and apply it to different types inΒ signingConfig.

Let’s move on β€” configuring the compatible Java version (for Kotlin 2.0+, this is, at a minimum, version 11):

compileOptions {
  sourceCompatibility = JavaVersion.VERSION_11
  targetCompatibility = JavaVersion.VERSION_11
}

kotlinOptions {
  jvmTarget = JavaVersion.VERSION_11.toString()
}

I recommend putting this in constants somewhere at theΒ root/build.gradle.ktsΒ level or inΒ buildSrcΒ orΒ build-logic, if your project has them.

Another important block for configuration isΒ buildFeatures. Below, I will list the most popular flags that can be configured in this section:

  • aidlΒ enables AIDL support (inter-application-process communication viaΒ .aidlΒ files).
  • buildConfigΒ generates aΒ BuildConfigΒ class with constants (disabled in new versions becauseΒ BuildConfigΒ is deprecated).
  • composeΒ enables Jetpack Compose support.
  • dataBindingΒ enables Android DataBinding (do not confuse with ViewBinding).
  • viewBindingΒ enables ViewBinding (safe access to Views withoutΒ findViewById).

The last block isΒ composeOptions. It’s simple here; it serves to configure Compose in the project:

  • kotlinCompilerExtensionVersionβ€”specifies the compatible version of the Kotlin compiler plugin.
  • kotlinCompilerVersionβ€”specifies the compatible Kotlin version.
  • useLiveLiteralsβ€”enables hot reload support for string literals, numbers, etc.

I think the last flag will be the most useful πŸ™‚

Dependencies, where would we be without them?

We’ve covered all possible and impossible configurations above, and now for the most important thing that we use Gradle for 90% of the time β€”Β dependencies.

Press enter or click to view image in full size

source

Let’s briefly consider 2 cases: configuring aΒ library-moduleΒ and anΒ app-moduleΒ (let’s start with the app):

dependencies {
    implementation(projects.libModule1)
    implementation(projects.libModule2)

    /// other library modules
    implementation(libs.bundles.androidx)
    implementation(libs.bundles.lifecycle)
    implementation(libs.bundles.kotlinx)

    implementation(platform(libs.firebase.bom))
    implementation(libs.firebase.auth)
    implementation(libs.firebase.crashlytics)
    implementation(libs.firebase.analytics)

    implementation(libs.room.ktx)
    implementation(libs.room.runtime)
    ksp(libs.room.compiler)

    implementation(libs.hilt.android)
    implementation(libs.hilt.compose)
    ksp(libs.hilt.compiler)

    /// ...
}

Since all our dependencies are in the version catalog, we access them through the generated type-safe accessors, which also work well with autocompletion in the IDE, so we don’t have to remember what everything is called.

Regarding dependency configuration in aΒ library-module, if your project already has modules, I recommend moving common libraries to one that is named something likeΒ commonΒ orΒ coreΒ (for example, you can moveΒ kotlinxΒ configuration orΒ lifecycleΒ dependencies, or evenΒ retrofit/ktorβ€”in general, everything that is basic can be in a basic module):

dependencies {
    api(libs.bundles.androidx)
    api(libs.bundles.lifecycle)
    api(libs.bundles.kotlinx)
}

Now, theΒ app-moduleΒ configuration will look simpler:

dependencies {
    implementation(projects.common)
    /// ...

    implementation(platform(libs.firebase.bom))
    implementation(libs.firebase.auth)
    implementation(libs.firebase.crashlytics)
    implementation(libs.firebase.analytics)

    /// ...
}

In more complex cases, you can move this toΒ build-logicΒ and create a plugin there that will set up all the main dependencies.

What else can be configured?

Some plugins provide the ability to configure themselves by adding additional sections to the config:

composeCompiler {
    reportsDestination = layout.buildDirectory.dir("compose_compiler")
}

room {
    schemaDirectory("$projectDir/schemas")
}

This is usually indicated directly in the documentation, so I want to move on to less obvious but no less useful things:

The name of the output file

android {
    applicationVariants.all {
        val variant = this
        val formattedName = "app-${it.name}-$versionName-$versionCode"
    
        variant.outputs.all {
            this as ApkVariantOutputImpl
            outputFileName = "$formattedName.apk"
        }
    
        project.tasks.named("sign${variant.name.capitalized()}Bundle", FinalizeBundleTask::class.java) {
            val artifactDestination = finalBundleFile.asFile.get().parentFile
            finalBundleFile.set(File(artifactDestination, "$formattedName.aab"))
        }
    }
}

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

, ,

Kobweb:Creating websites in Kotlin leveraging Compose HTML

Kobweb is a Kotlin web framework that aims to make web development enjoyable by building on top of Compose HTML and drawing inspiration from Jetpack Compose.
Watch Video

Kobweb:Creating websites in Kotlin leveraging Compose HTML

David Herman
Ex-Googler, author of Kobweb

Kobweb:Creating websites in Kotlin leveraging Compose HTML

David Herman
Ex-Googler, author o ...

Kobweb:Creating websites in Kotlin leveraging Compose HTML

David Herman
Ex-Googler, author of Kob ...

Jobs

In this way, we can set the name of the artifact for different build types (release/debug/custom) with support for app bundles and the old APK format. This is very convenient for CI, when we also specify the version in the artifact name, so QA or a customer can distinguish what kind of build it is.

Version and code can be passed as script parameters

Usually, we manually setΒ versionNameΒ andΒ versionCode, but what if we have CI and want to automatically set the build code? This can be easily done like this:

android {
    defaultConfig {
        versionCode = applicationVersionCode
        versionName = applicationVersionName
    }
}

/// ...

val Project.applicationVersionCode: Int
    get() = obtainPropertyBy<String>("versionCode")?.toInt() ?: 1

val Project.applicationVersionName: String
    get() = obtainPropertyBy<String>("versionName") ?: "dev build"

inline fun <reified T : Any> Project.obtainPropertyBy(name: String): T? {
    return when {
        hasProperty(name) -> property(name) as T
        else -> null
    }
}

In such a configuration, on CI, we can pass parameters through bash/shell scripts (or through the custom tasks of your CI provider), and they will automatically get into the necessary fields of the artifact. The same applies toΒ signingConfigβ€”we can pass it in the parameters.

What does it all look like together?

Finally, I want to show you what my Gradle KTS file looks like after all the work:

plugins {
    alias(libs.plugins.android.application.gradle.plugin)
    alias(libs.plugins.kotlin.android.gradle.plugin)
    alias(libs.plugins.ksp.devtools.gradle.plugin)
    alias(libs.plugins.compose.compiler.gradle.plugin)
    alias(libs.plugins.dagger.hilt.gradle.plugin)
}

android {
    namespace = "com.sagrishin.mycleanapp"
    compileSdk = 35

    defaultConfig {
        applicationId = "com.sagrishin.mycleanapp"

        minSdk = 24
        targetSdk = 35

        versionCode = applicationVersionCode
        versionName = applicationVersionName
    }

    applicationVariants.all {
        val variant = this
        val formattedName = "app-${it.name}-$versionName-$versionCode"
    
        variant.outputs.all {
            this as ApkVariantOutputImpl
            outputFileName = "$formattedName.apk"
        }
    
        project.tasks.named("sign${variant.name.capitalized()}Bundle", FinalizeBundleTask::class.java) {
            val artifactDestination = finalBundleFile.asFile.get().parentFile
            finalBundleFile.set(File(artifactDestination, "$formattedName.aab"))
        }
    }

    buildTypes {
        release {
            isMinifyEnabled = false
            proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")

            buildConfigField("String", "API_URL", "\"https://api.hostname.com/api\"")
            resValue("string", "push_notification_activity", "$namespace.HOME_ACTIVITY")
        }

        debug {
            buildConfigField("String", "API_URL", "\"https://dev.hostname.com/api\"")
            resValue("string", "push_notification_activity", "$namespace.HOME_ACTIVITY")
        }
    }

    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
    }

    kotlinOptions {
        jvmTarget = JavaVersion.VERSION_17.toString()
    }

    packaging {
        resources {
            excludes += "/META-INF/{AL2.0,LGPL2.1}"
        }
    }

    buildFeatures {
        compose = true
        buildConfig = true
    }
}

dependencies {
    implementation(libs.kotlin.reflect)
    implementation(libs.bundles.kotlinx)
    implementation(libs.bundles.androidx)
    implementation(libs.bundles.lifecycle)
    implementation(libs.bundles.networking)

    implementation(platform(libs.compose.bom))
    implementation(libs.bundles.compose)
    debugImplementation(libs.compose.ui.tooling)

    implementation(platform(libs.firebase.bom))
    implementation(libs.firebase.auth.ktx)
    implementation(libs.firebase.crashlytics.ktx)
    implementation(libs.firebase.firestore.ktx)
    implementation(libs.firebase.analytics.ktx)

    implementation(libs.gms.play.services.auth)

    implementation(libs.localstorage.room.runtime)
    implementation(libs.localstorage.room.ktx)
    ksp(libs.localstorage.room.compiler)

    implementation(libs.hilt.android)
    implementation(libs.hilt.compose)
    ksp(libs.hilt.compiler)

    implementation(libs.navigation.ui.ktx)
    implementation(libs.navigation.compose)

    implementation(libs.google.play.update)
    implementation(libs.google.play.update.ktx)

    implementation(libs.serialization.gson)
}

room {
    schemaDirectory("$projectDir/schemas")
}

composeCompiler {
    reportsDestination = layout.buildDirectory.dir("compose_compiler")
}

That’s all from me. Thank you very much for reading my article to the end! I would also be grateful for a like if it was useful!

This article was previously published on proandroiddev.com.

Menu