Previously with Groovy, you could easily move parts of your build.gradle to separate files. But now using the Kotlin DSL I get unresolved references and I can't seem to figure out why.
If I have this in my app/build.gradle.kts file, everything compiles nicely.
/**
* Apply shared configurations to the android modules.
*/
fun com.android.build.gradle.BaseExtension.baseConfig() {
plugins.withType<com.android.build.gradle.api.AndroidBasePlugin> {
configure<com.android.build.gradle.BaseExtension> {
defaultConfig {
compileOptions {
compileSdkVersion(33)
defaultConfig {
minSdk = 23
targetSdk = 33
}
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
}
}
}
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile> {
kotlinOptions.jvmTarget = "11"
}
}
/**
* Apply configuration settings that are shared across all modules.
*/
fun PluginContainer.applyBaseConfig(project: Project) {
whenPluginAdded {
when (this) {
is com.android.build.gradle.internal.plugins.AppPlugin -> {
project.extensions
.getByType<com.android.build.gradle.AppExtension>()
.apply {
baseConfig()
}
}
is com.android.build.gradle.internal.plugins.LibraryPlugin -> {
project.extensions
.getByType<com.android.build.gradle.LibraryExtension>()
.apply {
baseConfig()
}
}
}
}
}
But as soon as I move these into gradle/gradle-base-config.gradle.kts, it will no longer compile. I'm assuming because it has no reference to what it is etc, but I can't find anything anywhere on how to expose this. This also happens for configurations should as jacoco configurations when using apply(from = "${projectDir}/gradle/jacoco.gradle.kts there's unresolved references in the kts file, but moving it directly into my app build.gradle everything compiles.
This is unfortunately not supported by kotlin dsl as described here: type safe accessors.
However you can use precompiled script plugins to achieve similar behavior
Related
I have separate groovy script that adds flavors to my android project. And i can apply it no problem in build.gradle files. But calling it from kotlin build.gradle.kts file im getting error.
I have flavors.gradle file
ext.configFlavors = {
flavorDimensions "brand"
productFlavors {
myBrand {
dimension "brand"
}
}
And i can easilly call this from build.gradle files
android{
...
with configFlavors
}
But in build.gradle.kts files i get:
Could not find method flavorDimensions() for arguments [brand] on project ': myModule' of type org.gradle.api.Project.
My build.gradle.kts looks like this:
import com.android.build.gradle.LibraryExtension
plugins {
id("com.android.library")
id("org.jetbrains.kotlin.android")
}
apply(from ="${rootProject.projectDir}/buildSrc/flavors.gradle" )
val configFlavors: groovy.lang.Closure<LibraryExtension> by extra
android {
compileSdk = Versions.compile_sdk_version
defaultConfig {
minSdk = Versions.min_sdk_version
targetSdk = Versions.target_sdk_version
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}
buildTypes {
//Dev
create("dev") {
isMinifyEnabled = false
proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
}
//Staging
create("staging") {
isMinifyEnabled = false
proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
}
//Production
create("production") {
isMinifyEnabled = false
proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
}
}
// I am able to call the closure but i got the error
configFlavors()
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
buildFeatures {
viewBinding = true
}
}
dependencies {
implementation(fileTree("include" to listOf("*.jar"), "dir" to "libs"))
implementation(project(":domain"))
// list of dependencies
}
Extra properties can be accessed with the following syntax:
// get an extra property from the current project
val myProperty: String by extra
// or from the root project
val myNewProperty: String by rootProject.extra
However, configFlavours is not a simple primitive value, it's a Closure.
It's possible to access Closures in Kotlin, but without knowing what type the Closure should apply to, it's not possible to use it.
// Replace `Any` with the actual type, if known
val configFlavours: groovy.lang.Closure<Any> by extra
println(configFlavours)
// > Configure project :
// flavours_atn69ibjqzvnwf0cp7hb75l45$_run_closure1#398b577e
There are more through examples in the Gradle docs
P.S. your code has both flavour and flavor - pick a spelling please :)
I ended up adding another closure that takes libraryExtension as parameter. And using it from gradle.build.kts files. It is not optimal but it seems to work.
ext.configFlavorsWithParam = { libraryExtension ->
libraryExtension.flavorDimensions "brand"
libraryExtension.productFlavors {
brand1 {
dimension "brand"
}
}
And im calling it like this
android{
...
configFlavorsWithParam(this)
}
I was writing my own navigation for compose, by that ive created a seperate project that contains the library. after some time i could reuse the project in an compose desktop application. after some time i recieved emails about my project not running on andoid anymore. ive got this stacktrace: (other versions results in othewr method names but everytime method not found)
No virtual method setUi(Lkotlin/jvm/functions/Function5;)V in class Lcom/nom/rougthnav/ScreenConfiguration; or its super classes (declaration of 'com.nom.rougthnav.ScreenConfiguration' appears in /data/data/com.nom.myapplication/code_cache/.overlay/base.apk/classes.dex)
at com.nom.myapplication.MainActivity$onCreate$1$1$1$router$2$1.invoke(MainActivity.kt:33)
at com.nom.myapplication.MainActivity$onCreate$1$1$1$router$2$1.invoke(MainActivity.kt:31)
at com.nom.rougthnav.Router.screen(Router.kt:75)
at com.nom.rougthnav.Router.screen(Router.kt:47)
this keeps appearing only if i use this as an aar library, so ive researched about this.
ive analyzed my aar files and the final apk that got installed everywhere is the method present and i have no dependency version conflict. if i use the files directly in those projects its working like a charm.
ive thought that this might be resolved if i use another set of dependencies, so ive created a new sample project barely used anything, but got this error again in any version of my aar file.
an example:
ive got this interface for the configuration:
interface RouterScope<K> {
fun <T> screen(
tag: K,
modelFactory: () -> T,
block: #Composable (model: T, back: Back, nav: Nav<K>) -> Unit
)
var initialTag: K
var link: String
fun route(tag: K, initial: K, block: RouterScope<K>.() -> Unit)
var tagConverter: ((String) -> K)?
}
but the screen method keeps disappearing. im wondering if this is linked with the composeable annotation. But the composeable annotation is used the same way in other android libs.
Any hint appreciated, im finally stuck. here is my build.gradle.kts:
plugins {
id("org.jetbrains.kotlin.android")
id("com.android.library")
`maven-publish`
}
android {
compileSdk = 30
defaultConfig {
// applicationId = "com.nom.rougthnav"
minSdk = 27
targetSdk = 30
// versionCode = 1
// versionName = "1.0"
version = "1.6"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "11"
}
}
dependencies {
implementation("androidx.core:core-ktx:1.6.0")
implementation("androidx.appcompat:appcompat:1.3.1")
implementation("com.google.android.material:material:1.4.0")
val compose = "1.0.3"
implementation("androidx.compose.ui:ui:$compose")
implementation("androidx.compose.ui:ui-tooling-preview:$compose")
implementation("androidx.compose.ui:ui-tooling:$compose")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.3")
androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0")
}
afterEvaluate {
publishing {
publications {
register("release",MavenPublication::class){
from(components["release"])
groupId = "com.nom.roughtnav"
artifactId = "core"
version = "${project.version}"
}
//
}
}
}
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 {
...
}
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.
Does anyone know the syntax for defining flavor dimensions and variant filters using the experimental Gradle plugin? (This would include the syntax for assigning a flavor to a particular dimension inside each flavor's create() block.)
The experimental plugin user guide doesn't address any of this and I couldn't find any examples in the code samples. I'm using plugin version 0.2.1 (com.android.tools.build:gradle-experimental:0.2.1). The syntax for the regular plugin clearly doesn't work at all.
Here's the original script, which defines product flavors for Google Play and for Amazon AppStore:
apply plugin: 'com.android.model.application'
model {
android {
compileSdkVersion = 23
buildToolsVersion = '23.0.1'
defaultConfig.with {
... // normal stuff
}
}
android.aaptOptions {
noCompress = 'dict'
}
android.ndk {
... // normal stuff
}
android.compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_7
targetCompatibility = JavaVersion.VERSION_1_7
}
android.buildTypes {
release {
minifyEnabled = true
proguardFiles += file('proguard-rules.pro')
}
}
android.productFlavors {
create("google") {
}
create("amazon") {
}
}
}
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
compile 'com.android.support:support-v4:23.1.0'
compile 'com.android.support:support-annotations:23.1.0'
}
Since this app uses native code, I want to add a dimension for different hardware platforms. Also, since the Amazon AppStore and Google Play have different models for supporting different hardware, I want to use variant filters to treat hardware platforms differently for the different markets.
I've stumbled about blindly in the dark tried a number of things to define flavor dimensions; these all generate errors when compiling the Gradle script.
I haven't tried creating variant filters yet, since I'm stuck at step 1, but absent guidance from someone "in the know", I expect equal (lack of) success.
Attempt 1:
apply plugin: 'com.android.model.application'
model {
android {
...
defaultConfig.with {
...
flavorDimensions = ["abi", "market"]
}
}
...
}
...
Error:
No such property: flavorDimensions for class: com.android.build.gradle.managed.ProductFlavor
Attempt 2:
apply plugin: 'com.android.model.application'
model {
android {
...
defaultConfig.with {
...
}
flavorDimensions = ["abi", "market"]
}
...
}
...
Error:
No such property: flavorDimensions for class: com.android.build.gradle.managed.AndroidConfig
Attempt 3:
apply plugin: 'com.android.model.application'
model {
android {
... // as in original script
}
android.flavorDimensions {
create("abi") {}
create("market") {}
}
}
...
Error:
A problem occurred configuring project ':app'.
The following model rules are unbound:
model.android.flavorDimensions
Mutable:
- android.flavorDimensions (java.lang.Object)
Attempt 4:
apply plugin: 'com.android.model.application'
model {
android {
...
}
android.flavorDimensions = ["abi", "market"]
}
Error:
No such property: flavorDimensions for class: org.gradle.model.dsl.internal.NonTransformedModelDslBacking
I also had this issue today. Finally, I found that you don't need to specify android.flavorDimensions. You only need to simply add dimension to each flavors.
I am using experimental experimental plugin 0.2.1 with gradle-2.5.
android.productFlavors {
create("product1"){
dimension = "product"
}
create("product"){
dimension = "product"
}
create("arm"){
dimension = "abi"
}
create("armv7"){
dimension = "abi"
}
}