I'm trying to create a Flavor configuration to avoid boilerplate code in every Module and Library gradle file.
To do so I'm trying to convert Piotr Zawadzki solution (https://medium.com/stepstone-tech/modularizing-your-flavored-android-project-5db75c59fb0d) which uses the groovy with() method combined with a Closure containing the flavor config.
ext.flavorConfig = { // 1
flavorDimensions "pricing"
productFlavors {
free {
dimension "pricing"
ext.myApplicationIdSuffix = '.free' // 2
}
paid {
dimension "pricing"
ext.myApplicationIdSuffix = '.paid'
}
}
productFlavors.all { flavor -> // 3
if (flavor.hasProperty('myApplicationIdSuffix') && isApplicationProject()) {
flavor.applicationIdSuffix = flavor.myApplicationIdSuffix
}
}
}
def isApplicationProject() { // 4
return project.android.class.simpleName.startsWith('BaseAppModuleExtension')
// in AGP 3.1.x with library modules instead of feature modules:
// return project.android instanceof com.android.build.gradle.AppExtension
}
What I'm not finding is the equivalent with() method for the Kotlin DSL or a proper way to translate the Closure.
An equivalent should be apply or run, depending on what's the actual return value of with (which I couldn't figure out for some reason).
I found a solution with KotlinScript, I think it could be done with Groovy too.
First, add gradle-api dependency in your buildSrc/build.gradle.kts file
//buildSrc/build.gradle.kts
val agpVersion = "7.1.1"
plugins {
`kotlin-dsl`
}
repositories {
mavenCentral()
google()
}
dependencies{
implementation("com.android.tools.build:gradle-api:$agpVersion")
}
Then create a Gradle Plug-In in buildSrc.
//buildSrc/src/main/kotlin/FooPlugin.kt
abstract class FooPlugin : Plugin<Project> {
override fun apply(project: Project) {
val android = project.extensions.getByType(ApplicationExtension::class.java)
android.defaultConfig {
android.productFlavors.create("Bar")
}
}
}
Finally apply FooPlugin to your application's build.gradle.kts
//baz/build.gradle.kts
plugins {
...
}
apply<FooPlugin>()
android{
...
}
dependencies {
...
}
Related
I spend hours on just setting up Protobuf with Kotlin in Android Studio. The endgoal is just that my proto files are compiled in Kotlin and that I can use them in my project.
I have an example project here: https://github.com/Jasperav/ProtobufAndroid. It mimics my setup in the real application: an external dir containing the proto files and the android project. It contains all the code mentioned below. This is a combined effort of tutorials I found on the internet. It is probably terrible wrong. I tried https://github.com/google/protobuf-gradle-plugin, but it just looks so complicated for something simple I am doing:
Have a dir with protofiles somewhere on your filesystem
Create a new Android project on Kotlin
In the Project build.gradle, add id 'com.google.protobuf' version '0.9.2' apply false as plugin
In the Module build.gradle, add ->
This to the dependencies: implementation 'com.google.protobuf:protobuf-lite:3.21.12'
The sourceSets at the bottom inside the android bracket
The protobuf section at the bottom between the dependencies and android section.
sourceSets:
sourceSets {
main {
kotlin {
srcDirs += 'build/generated/source/proto/main/kotlin'
}
proto {
srcDir '/Users/me/androidkotlin/proto'
}
}
}
protobuf:
protobuf {
protoc {
artifact = 'com.google.protobuf:protoc:3.21.12'
}
plugins {
kotlinlite {
artifact = 'com.google.protobuf:protoc-gen-kotlin:3.21.12'
}
}
generateProtoTasks {
ofSourceSet("main").forEach { task ->
task.builtins {
getByName("kotlin") {
option("lite")
}
}
}
}
}
I get this error:
A problem occurred evaluating project ':app'.
> Could not find method proto() for arguments [build_cxwfo79b6zcc266x9rsqzou9f$_run_closure1$_closure8$_closure10$_closure12#203aac02] on source set main of type com.android.build.gradle.internal.api.DefaultAndroidSourceSet.
You are in a good way, but, there is some stuff missing:
The gradle code I'll share is written in Kotlin, just in case. If you can convert your grade files to Kotlin, nice, if not you have to convert them to groovy.
The first thing to check is if you have the proto folder in the right path, it should be in
root->app->src->main->proto
In the project gradle make sure to have the plugin applied
id("com.google.protobuf") version "0.8.15" apply false
In the app gradle, make sure to have the following.
import com.google.protobuf.gradle.*
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("com.google.protobuf")
}
The dependencies:
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.2")
implementation("com.google.protobuf:protobuf-kotlin:3.21.2")
implementation("io.grpc:grpc-stub:1.52.0")
implementation("io.grpc:grpc-protobuf:1.52.0")
implementation("io.grpc:grpc-okhttp:1.52.0")
implementation("com.google.protobuf:protobuf-java-util:3.21.7")
implementation("com.google.protobuf:protobuf-kotlin:3.21.2")
implementation("io.grpc:grpc-kotlin-stub:1.3.0")
And the protobuf task:
protobuf {
protoc {
artifact = "com.google.protobuf:protoc:${rootProject.ext["protobufVersion"]}"
}
plugins {
id("java") {
artifact = "io.grpc:protoc-gen-grpc-java:${rootProject.ext["grpcVersion"]}"
}
id("grpc") {
artifact = "io.grpc:protoc-gen-grpc-java:${rootProject.ext["grpcVersion"]}"
}
id("grpckt") {
artifact = "io.grpc:protoc-gen-grpc-kotlin:${rootProject.ext["grpcKotlinVersion"]}:jdk8#jar"
}
}
generateProtoTasks {
all().forEach {
it.plugins {
id("java") {
option("lite")
}
id("grpc") {
option("lite")
}
id("grpckt") {
option("lite")
}
}
it.builtins {
id("kotlin") {
option("lite")
}
}
}
}
}
These are the versions I'm using:
ext["grpcVersion"] = "1.47.0"
ext["grpcKotlinVersion"] = "1.3.0" // CURRENT_GRPC_KOTLIN_VERSION
ext["protobufVersion"] = "3.21.2"
ext["coroutinesVersion"] = "1.6.2"
Having that your project should generate the code based on your proto files.
For further reference, I recently build this Android App based on Kotlin + gRPC: https://github.com/wilsoncastiblanco/notes-grpc
I'm trying to set androidResources.noCompress for only one product flavor
When I try this
flavorDimensions "ring"
productFlavors {
innerRing {
dimension "ring"
}
outerRing {
dimension "ring"
androidResources {
noCompress 'so'
}
}
}
Both innerRing and outerRing end up with .so files uncompressed. I believe this is due to gradle configuring all the product flavors statically. (see here)
But when I try to change noCompress afterEvaluate
afterEvaluate {
android.applicationVariants.all { variant ->
def ring = variant.getProductFlavors().get(0).name
if (ring == "outerRing") {
println("Don't compress .so files for outer ring build")
android.androidResources.noCompress = ['so']
}
}
}
I get this error
com.android.build.gradle.internal.dsl.AgpDslLockedException:
It is too late to modify internalNoCompressList
It has already been read to configure this project.
Consider either moving this call to be during evaluation,
or using the variant API.
How can I use the variant API to fix this?
Any help is appreciated!
If you are on AGP 7.2
android {
...
androidComponents {
onVariants(selector().all()) { variant ->
if(variant.name.startsWith("outerRing")) {
variant.androidResources.noCompress.add('so')
}
}
}
...
}
If AGP is below 7.2 then from the documentation theoretically the following code should work, but I've had no success with it
android {
...
androidComponents {
onVariants(selector().all()) { variant ->
if(variant.name.startsWith("outerRing")) {
def aaptParams = variant.androidResources.aaptAdditionalParameters
aaptParams.add("-0")
aaptParams.add("so")
}
}
}
...
}
I'm experimenting with Kotlin DSL but I can't get it to recognize objects I defined in buildSrc. They get resolved by the IDE but when I compile the code it doesn't work.
This is the my project structure:
build.gradle.kts
settings.gradle.kts
+buildSrc
build.gradle.kts
+src
+main
+java
Dependencies.kt
Versions.kt
+module1
build.gradle.kts
+module2
build.gradle.kts
Content of Dependencies.kt:
/**
* To define plugins
*/
object BuildPlugins {
val gradle by lazy { "com.android.tools.build:gradle:${Versions.gradlePlugin}" }
val kotlinGradle by lazy { "org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlin}" }
val safeArgs by lazy { "androidx.navigation:navigation-safe-args-gradle-plugin:${Versions.safeArgs}" }
}
/**
* To define dependencies
*/
object Deps {
val appCompat by lazy { "androidx.appcompat:appcompat:${Versions.appCompat}" }
val core by lazy { "androidx.core:core-ktx:${Versions.core}" }
val timber by lazy { "com.jakewharton.timber:timber:${Versions.timber}" }
val kotlin by lazy { "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${Versions.kotlin}" }
val material by lazy { "com.google.android.material:material:${Versions.material}" }
val constraintLayout by lazy { "androidx.constraintlayout:constraintlayout:${Versions.constraintLayout}" }
}
Project wide build.gradle.kts (the one which fails first):
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
BuildPlugins.gradle
BuildPlugins.kotlinGradle
BuildPlugins.safeArgs
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
...
I also want to point out that the android { ... } block is not recognized in the modules gradle files but I think that may be because of the failed compilation.
You have Kotlin files under src/main/java. They should be in src/main/kotlin and you will need to include Kotlin build support in the buildSrc Gradle file (maybe you have this, you didn't show what's in buildSrc/build.gradle.kts)
plugins {
`kotlin-dsl`
}
repositories {
mavenCentral()
}
Every time I publish a Kotlin Multiplatform Mobile library to maven central the only I can seem to add/use the Android dependency in an Android app is by adding both the releaseImplementation and debugImplementation
Example
releaseImplementation 'io.github.tyczj.lumberjack:Lumberjack-android:1.0.0#aar'
debugImplementation 'io.github.tyczj.lumberjack:Lumberjack-android-debug:1.0.0#aar'
Instead of the "normal" way where you just have a single implementation
implementation 'io.github.tyczj.lumberjack:Lumberjack-android:1.0.0'
Here is my build.gradle file
plugins {
kotlin("multiplatform") version "1.4.32"
id("com.android.library")
id("io.github.gradle-nexus.publish-plugin") version "1.1.0"
id("maven-publish")
id("signing")
}
group = "io.github.tyczj.lumberjack"
version = "1.0.2"
ext["signing.keyId"] = ""
ext["signing.password"] = ""
ext["signing.secretKeyRingFile"] = ""
repositories {
google()
mavenCentral()
maven {
setUrl("https://plugins.gradle.org/m2/")
}
}
val javadocJar by tasks.registering(Jar::class) {
archiveClassifier.set("javadoc")
}
val emptyJar by tasks.registering(Jar::class) {
archiveAppendix.set("empty")
}
kotlin {
android{
publishLibraryVariants("release", "debug")
}
iosX64("ios") {
binaries {
framework {
baseName = "lumberjack"
}
}
}
sourceSets {
val commonMain by getting
val commonTest by getting
val androidMain by getting
val androidTest by getting
val iosMain by getting
val iosTest by getting
}
}
android {
compileSdkVersion(29)
sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
defaultConfig {
minSdkVersion(24)
targetSdkVersion(29)
}
buildTypes {
getByName("release") {
isMinifyEnabled = false
}
}
}
afterEvaluate {
publishing {
repositories {
maven {
name = "sonatype"
url = uri("https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/")
credentials {
username = rootProject.ext["ossrhUsername"]?.toString()
password = rootProject.ext["ossrhPassword"]?.toString()
}
}
}
publications.withType<MavenPublication> {
artifact(javadocJar.get())
pom{
name.set("Lumberjack")
description.set("Logging library for Kotlin Multiplatform Mobile applications")
url.set("https://github.com/tyczj/Lumberjack")
licenses {
license {
name.set("MIT")
url.set("https://opensource.org/licenses/MIT")
}
}
developers {
developer {
id.set("tyczj")
name.set("Jeff Tycz")
email.set("tyczj359#gmail.com")
}
}
scm {
url.set("https://github.com/tyczj/Lumberjack")
}
}
}
}
}
ext["signing.keyId"] = rootProject.ext["signing.keyId"]?.toString()
ext["signing.password"] = rootProject.ext["signing.password"]?.toString()
ext["signing.secretKeyRingFile"] = rootProject.ext["signing.secretKeyRingFile"]?.toString()
signing {
sign(publishing.publications)
}
apply(from = "${rootDir}/scripts/publish-root.gradle")
The full source for this library can be found here
What is wrong with how I am building/publishing KMM libraries where I have to specify the release and debug implementations?
You should not specify -android postfix, just use implementation("io.github.tyczj.lumberjack:Lumberjack:1.0.0").
This works because dependency variant resolution is based on Gradle Module Metadata. This metadata is just another file published with your library (it has .module extension) and it contains description of all variants. As you are publishing the library as a whole, the top-level artifact io.github.tyczj.lumberjack:Lumberjack contains the metadata for the whole library, allowing gradle to choose the right variant.
Another option would be to make sure your -android artifact contains proper module metadata with both release and debug variants. I believe publishLibraryVariantsGroupedByFlavor is the way to tell the publisher plugin to make it this way, but I have not tried it.
I have a multi-module Android project & Kotlin Gradle DSL. There is some configuration which has to be repeated in every module and I would like to reuse the code. I would like to reuse for example this code:
android {
sourceSets {
getByName("main").java.srcDirs("src/main/kotlin")
getByName("test").java.srcDirs("src/test/kotlin")
}
}
There are two methods documented in Kotlin DSL samples:
apply(from = "foo.gradle.kts")
and extension functions in buildSrc like this:
fun Project.kotlinProject() {
dependencies {
"compile"(kotlin("stdlib"))
}
}
However both these methods work only for top-level configuration, I can't access Android plugin's stuff. I'm getting errors like Unresolved reference: BaseExtension
At the end I was inspired by SUPERCILEX's code:
allprojects {
val parent = (group as String).split(".").getOrNull(1)
when {
name == "app" -> {
apply(plugin = "com.android.application")
configureAndroidModule()
}
parent == "common-android" -> {
apply(plugin = "com.android.library")
configureAndroidModule()
}
}
}
fun Project.configureAndroidModule() {
configure<BaseExtension> {
sourceSets {
getByName("main").java.srcDirs("src/main/kotlin")
getByName("test").java.srcDirs("src/test/kotlin")
}
}
}
How about using subprojects block? I have a multi-module Android project and this is how I reuse code in my build scripts.
subprojects {
apply plugin: 'com.android.library'
android {
sourceSets {
getByName("main").java.srcDirs("src/main/kotlin")
getByName("test").java.srcDirs("src/test/kotlin")
}
}
}
Unresolved reference: BaseExtension
As for the above error message, if you want to use android block, you should declare your modules as android application or library by applying plugins like above build script.
If you want the settings to be repeated in only some of the modules, you can use configure block like this:
configure(subprojects - project(':${module_name}')) {
dependencies {
implementation 'com.x.y.z:abc:1.0.0'
}
}
The above block will define the dependency on all modules except the module with the given name.