Use TOML vesion file in gradle - android

I try use version from libs.versions.toml to set composeOptions like this:
android {
...
composeOptions {
kotlinCompilerExtensionVersion = libs.versions.kotlinCompilerExtensionVersion.get()
}
}
But I get error with .get() function :
Unresolved reference. None of the following candidates is applicable
because of receiver type mismatch: public inline operator fun <K, V>
Map<out TypeVariable(K), TypeVariable(V)>.get(key: TypeVariable(K)):
TypeVariable(V)? defined in kotlin.collections public operator fun
MatchGroupCollection.get(name: String): MatchGroup? defined in
kotlin.text
TOML versioning working well when a use this to apply plugins and implement libraries but not working when I want to get a version with .get()
Gradle version 7.5.1

If you get Unresolved reference. None of the following candidates is applicable receiver type mismatch... when you are trying to get a version of library xxx from your Gradle Version Catalogs via .get() method, in 99% of cases it means that you have library xxx-yyy in your [versions] block, e.g.
[versions]
kotlin = "1.7.20"
kotlin-coroutines = "1.6.4"
kotlin-serialization = "1.4.1"
In that case Gradle won't generate regular Provider for the field libs.versions.kotlin (in the example), it generates more complex object, because it has to generate libs.versions.kotlin.coroutines, libs.versions.kotlin.serialization fields.
That's why if you want to get library version you have to explicitly ask for provider using .asProvider() before getter .get():
libs.versions.kotlin.asProvider().get()
and for library in question it will be:
libs.versions.kotlinCompilerExtensionVersion.asProvider().get()

Related

Android: Add maven-publish configuration in a separate kotlin dsl script

I've written a .gradle script named publish.gradle which configures publishing {} for releasing my artifact.
Why on a separate script? I have multiple modules and by doing this every releasable module simply defines some variables.
Module build.gradle.kts:
// Module's blah blah
apply(from = "../publish.gradle")
publish.gradle:
apply plugin: 'maven-publish'
publishing {
publications {
// configure release process
}
}
I've recently decided to migrate to Gradle Kotlin DSL. However, there's an issue:
Adding publication {} like this:
plugins {
`maven-publish`
}
publication {
}
Lead to this error:
Expression 'publishing' cannot be invoked as a function. The function 'invoke()' is not found
Unresolved reference. None of the following candidates is applicable because of receiver type mismatch:
public val PluginDependenciesSpec.publishing: PluginDependencySpec defined in org.gradle.kotlin.ds
Which is summarized to
PluginDependenciesSpec is not present as a receiver
What is the difference?
TL; DR
I've added publishing {} config to a separate script which works when in .gradle groovy format but I can not convert to .gradle.kts kotlin format. The publishing is extension of PluginDependenciesSpec class which is not present in the script.
Here's what worked for me:
plugins {
id("maven-publish")
}
configure<PublishingExtension> {
publications.create<MavenPublication>("myPlugin") {
groupId = "com.myCompany.android"
artifactId = "MyPlugin"
version = "1.0.0"
pom.packaging = "jar"
artifact("$buildDir/libs/MyPlugin.jar")
}
repositories {
mavenLocal()
}
}
I understand where you're coming from, converting from groovy to kotlin script is not a simple one to one translation, and most of us, including myself, code by example. In other words, you just need to see a simple example and you can figure out the rest. This works great when examples are readily available. What you need to do when you don't find an example is to turn to the API document. For example, https://docs.gradle.org/current/dsl/org.gradle.api.publish.PublishingExtension.html shows you the available properties for the PublishingExtension and you can keep drilling in to see what properties you have at the next level. This is especially important when examples may be working with an older version and may no longer be applicable. I will say that it wasn't as obvious is that for accessing extensions in kotlin script, requires the configure block. That's a big difference, but I like that approach, because it makes it clearer what the extension properties are a part of. And by the way, kotlin wants double quote, single quotes are no longer acceptable.

How to access Android's "dynamicFeatures" property from a custom Gradle plugin in buildSrc

In my project, I'd like to generate a class that contains information about my dynamic features. Dynamic features are added this way:
// In the base module’s build.gradle file.
android {
...
// Specifies dynamic feature modules that have a dependency on
// this base module.
dynamicFeatures = [":dynamic_feature", ":dynamic_feature2"]
}
Source: https://developer.android.com/guide/app-bundle/at-install-delivery#base_feature_relationship
I've been searching for solutions since a few days now, and I didn't find much. Currently, my plugin looks like this:
class MyPlugin : Plugin<Project> {
override fun apply(project: Project) {
if (project == rootProject) {
throw Exception("This plugin cannot be applied to root project")
}
val parent = project.parent ?: throw Exception("Parent of project cannot be null")
val extension = project.extensions.getByName("android") as BaseAppModuleExtension?
?: throw Exception("Android extension cannot be null")
extension.dynamicFeatures
}
}
Unfortunately, extension.dynamicFeatures is empty even if my plugin is applied to the build.gradle file having dynamic features.
It's empty, because you are trying to get extension value at the gradle lifecycle configuration stage, all gradle properties have not configured yet.
Use afterEvaluate closure. In this block dynamicFeatures has already configured and not empty.
project.afterEvaluate {
val extension = project.extensions.getByType(BaseAppModuleExtension::class.java)
?: throw Exception("Android extension cannot be null")
extension.dynamicFeatures
}

How to determine build type in kotlin-multiplatform project

I’m working on a multiplaform project, iOS and JVM (I’m not targeting Android directly). Depending on the build type (debug or release) I want to configure the logging level (i.e. to print only errors in release). Since there is no a BuildConfig class available, how can I know from commonMain the build type?
Not a direct answer to the question, but for android/ios one can define a property like this:
in commonMain:
expect val isDebug: Boolean
in androidMain:
actual val isDebug = BuildConfig.DEBUG
in iosMain:
actual val isDebug = Platform.isDebugBinary
It is not possible right now for all possible targets. Build type is resolved only for android.
But you can create two versions of the module e.g. module and module-debug.
Then declare in module:
const val IS_DEBUG = false
in module-debug:
const val IS_DEBUG = true
Finally in the resulting application or module gradle configuration you can declare dependency on what you need. E.g.:
if (DEBUG_ENV) // you need to set DEBUG_ENV from property or environment variable
implementation(project(":module-debug"))
else
implementation(project(":module"))
or for android:
debugImplementation(project(":module-debug"))
releaseImplementation(project(":module"))
This way you can change logic using IS_DEBUG constant for every target or can create even completely different implementations of something in debug and release modules.

Android and the Fabric (Crashlytics) plugin always generates a UUID (Gradle Kotlin DSL)

I want Fabric to stop generating a UUID on each build. What used to work with Gradle's Groovy DSL does not work with the newer Kotlin DSL. How can I achieve my goal with the Kotlin DSL?
(Gradle version 4.10.2, Fabric 1.25.4)
According to Fabric's documentation, you can add the following to your app's build script
android {
buildTypes {
debug {
// Only use this flag on builds you don't proguard or upload
// to beta-by-crashlytics
ext.alwaysUpdateBuildId = false
and this works. It prevents Fabric from generating a UUID on each debug build. However, if I convert my build script to Kotlin DSL, the following doesn't work
android {
buildTypes {
getByName("debug") {
// Only use this flag on builds you don't proguard or upload
// to beta-by-crashlytics
ext.set("alwaysUpdateBuildId", false)
Fabric ignores this value, now.
I have tried variations, such as the following:
project.ext.set("alwaysUpdateBuildId", false)
rootProject.ext.set("alwaysUpdateBuildId", false)
val alwaysUpdateBuildId by extra(false)
val alwaysUpdateBuildId by project.extra(false)
val alwaysUpdateBuildId by rootProject.extra(false)
None work.
For further reference, the Gradle task generating this value appears to be named :app:fabricGenerateResourcesDebug, and has type DefaultTask.
As Martin Rajniak mentioned, you can only call extra on ExtensionAware objects, with BuildType not being declared as one.
However, during runtime, build types actually are ExtensionAware, which is why this works in Groovy due to its dynamicity, but not in Kotlin where extra in this scope will reference the Project's extensions.
In order to achieve this without Groovy, we can simply cast the build type to ExtensionAware:
android {
buildTypes {
getByName("debug") {
(this as ExtensionAware).extra["alwaysUpdateBuildId"] = false
}
}
}
I have found a workaround to this problem. Create a file, fabric.gradle (Groovy build script!) and place it in your project structure somewhere. It will have the following contents:
// or "com.android.library"
project.pluginManager.withPlugin("com.android.application") {
android.buildTypes.debug.ext.alwaysUpdateBuildId = false
}
Now, in the build script for your module (let's call it app/build.gradle.kts), apply this script plugin:
apply(from = "path/to/fabric.gradle")
This workaround is based on the advice here, in the Kotlin DSL primer.

sbt-android: unable to use scala-reflect on android: java.rmi.Remote not found

I'm trying to use the scala-reflect package for android development.
I have added the scala-reflect dependency in my build.sbt:
libraryDependencies += "org.scala-lang" % "scala-reflect" % "2.11.8"
but I get an exception:
java.lang.NoClassDefFoundError: Failed resolution of: Ljava/rmi/Remote;
at scala.reflect.internal.Definitions$DefinitionsClass.RemoteInterfaceClass$lzycompute(Definitions.scala:370)
at scala.reflect.internal.Definitions$DefinitionsClass.RemoteInterfaceClass(Definitions.scala:370)
at scala.reflect.runtime.JavaUniverseForce$class.force(JavaUniverseForce.scala:255)
at scala.reflect.runtime.JavaUniverse.force(JavaUniverse.scala:16)
at scala.reflect.runtime.JavaUniverse.init(JavaUniverse.scala:147)
at scala.reflect.runtime.JavaUniverse.<init>(JavaUniverse.scala:78)
at scala.reflect.runtime.package$.universe$lzycompute(package.scala:17)
at scala.reflect.runtime.package$.universe(package.scala:17)
I have tried to add the java source of java.rmi.Remote and java.rmi.RemoteException and built the project with android:package --core-library (because sbt has not found the dexCoreLibrary option) , it builded successfully, but I got the runtime error.
So, is it possible to add the java.rmi dependency otherwise, that scala.reflect can use it?
I'm using the scala.reflect library in the context of an implementation of a method Option.orDefault:
class RichOption[+A : TypeTag](val delegate: Option[A]) {
def orDefault : A = delegate.getOrElse {
delegate match {
case t if typeOf[A] <:< typeOf[Int] => 0.asInstanceOf
case _ => throw new IllegalAccessException("there is no default value for this type.")
}
}
}
If you know a better implementation for Option.orDefault
(possibly without scala.reflect), please let me know.
Much better:
case class Default[A](value: A)
object Default {
implicit val intDefault: Default[Int] = Default(0)
// other Default implementations
}
class RichOption[+A](val delegate: Option[A])(implicit d: Default[A]) {
def orDefault : A = delegate.getOrElse(d.value)
}
You get a compilation error instead of a runtime error if used with a type which doesn't have a defined default value, no complex matching, no need for a large dependency, etc.

Categories

Resources