github blog

qq: 2383518170

wx: lzyprime

Lambda. :

Warehouse Address:Github.com/lzyprime/an…

The original idea was to split the compose version into a separate branch: dev_compose; It turns out that the dev branch is all the same code except for the View layer; Even some of the Compose components in the View layer are the same.

In the Model layer, there are frequent changes in data organization and encapsulation. In order to find a more reasonable and easy to use way, such as the provision and use of DataStore, several versions have been adjusted, and the current version is still not satisfactory.

If there are two branches, this part of the code synchronization is annoying. Git sub Module, Git rebase, manual copy. Neither is convenient.

So, the view outside part of the public, as a separate gradle module, compose the view part also smoke into a module. Then set up the dependencies in gradle scripts.

In the meantime, move gradle scripts from Groovy to KTS.

gradle kts

Gradle official website documentation

Migrating documents to the Android official website

After the migration, I found that the Android official website documentation also mentioned this.

benefits

  • Compared with thegroovyforkotlinMore familiar with. Script legibility improved, it is easier to grasp what is being performed in each step of the script, what is meant, click open to see the source code and comments.
  • More standard, de-sugared.groovyThere are a bunch of shortcuts for scripting, many of which rely on string parsing to see if it fits the rules and then calling the actual interface.
  • Indicates that the interface is invalid.gradleThe interface is about to be abandoned, the interface warning message, and so onkotlinCode as directly highlighted.
  • withKTSVersion to learngradleThe use of. And then even ifgroovyVersion, can also see a general, look at the official website documents and basic grammar can also write about. It may not be easy to write, but a decent script should work fine.

The bad

  • Compared with thegroovyIt must still be crude and imperfect. Include documentation, there is often only supportgroovyThe prompt.

The migration process

Gradle migrates documents

Add a. KTS suffix to the gradle file name (e.g. Build.gradle -> build.gradle.kts) and sync it to resolve any errors. It is better to change only one file at a time, otherwise the error is difficult to repair.

  • Strings must be filled with double quotes
  • Function calls are parenthesized. Such asclasspath.implementationAnd so on, Spaces followed by strings, usually function calls. toclasspath(xxx)style
  • Attribute value, such asminSdk.targetSdk.versionCodeAnd so on are made into attributes. And if the property isboolThe type, the name will becomeisXXXIn the form.
  • tasks.ext.extra.buildSrc

tasks

For each task, name:String, args:Map, and configureClosure: function. groovy provides a bunch of shortcuts, but it definitely falls into these three sections. Take Task Clean as an example.

// groovy
task clean(type: Delete) {
    delete rootProject.buildDir
}
Copy the code

Task (name:String) is used as a String. This is one of the ways Groovy provides convenient writing, string parsing. Finally equivalent to:

/ / pseudo code
task(
    args: {"type": Delete::class}, 
    name:"clean", 
    configureClosure: { // Delete
        delete(rootProject.buildDir)
    },
)
Copy the code

It’s really neat to write, like declaring a function. But look at the source code and so on, who knows what.

Kotlin also provides a bunch of shortcuts, in the form of an incline function, that can be used layer by layer to the end.

Gradle task documents

extThe problem

KTS also has ext functions, but if written in a buildScript block as before, an error will be reported. Click in to see why:

val org.gradle.api.Project.`ext`: org.gradle.api.plugins.ExtraPropertiesExtension get() =
    (this as org.gradle.api.plugins.ExtensionAware).extensions.getByName("ext") as org.gradle.api.plugins.ExtraPropertiesExtension

fun org.gradle.api.Project.`ext`(configure: Action<org.gradle.api.plugins.ExtraPropertiesExtension>): Unit =
    (this as org.gradle.api.plugins.ExtensionAware).extensions.configure("ext", configure)
Copy the code

This is an attempt to convert the current object to ExtensionAware. In Groovy, buildScript is the method of Project, which implements the ExtensionAware interface. In KTS, buildScript comes from the KotlinBuildScript abstract class, which is a ProjectDelegate that uses a delegate to access Project, and the base class is actually Project.

But the buildScript function accepts manipulation of ScriptHandlerScope types.

@Suppress("unused")
open fun buildscript(@Suppress("unused_parameter") block: ScriptHandlerScope. () - >Unit): Unit =
        internalError()

// use:
buildscript { // this: ScriptHandlerScope. }Copy the code

In other words, this in the code block is a ScriptHandlerScope and does not implement ExtensionAware. So ScriptHandlerScope as ExtensionAware failed.

This is why ext works in top-level blocks or in AllProjects blocks:

buildScript {... }// this: ProjectDelegate
ext {
    set("key"."value")
}

allprojects { // this: Project
    ext {
        set("k"."v")
    }
}

tasks.register<Delete>("clean") {
    rootProject.ext["key1"] / / specified Project
    delete(rootProject.buildDir)
}
Copy the code

But this is just when you define it, and if you use it, again because of this limitation, you need to see if the scope can be converted to ExtensionAware, and you need to know whose it is.

Because of the limitations of kotlin’s static language, it is not possible to use project.ext. key1 directly, or even project.key1. Have to Project. Ext (” key1 “).

tasks.register<Delete>("clean") {
    val key1 = rootProject.ext["key1"] / / specified Project
    delete(rootProject.buildDir)
}
Copy the code

But that doesn’t work in buildScript. At this point by ExtensionAware. Extentions. GetByName (ext) still can’t get. In fact, you can’t click into Groovy, so let’s see how Groovy handles this and achieves the effect of dynamic language.

ext -> extra

So this thing is basically dead. Then extra was offered.

buildscript {
    val gradleVersion by extra("7.0.1")
    val kotlinVersion by extra{ "1.5.21" }

    extra["activityVersion"] = "1.3.1"
    extra["lifecycleVersion"] = 2.3.1 ""
}

// module project
val kotlinVersion: String by rootProject.extra

val activityVersion: String by rootProject.extra
val lifecycleVersion: String by rootProject.extra
Copy the code

If you get the value as a delegate attribute. You need to explicitly declare the type. Source:

val ExtensionAware.extra: ExtraPropertiesExtension
    get() = extensions.extraProperties
Copy the code

Ext to get that is to say, actually is the same, Project. Ext is actually in the ExtensionAware. Extensions. ExtraProperties thrown out.

So the basic set get and so on still work. A bunch of delegate properties and functions have been added to facilitate the creation of fetch variables.

val kkk by extra(vvv):

// val kkk by extra(vvv)
operator fun <T> ExtraPropertiesExtension.invoke(initialValue: T): InitialValueExtraPropertyDelegateProvider<T> =
    InitialValueExtraPropertyDelegateProvider.of(this, initialValue)
    // InitialValueExtraPropertyDelegateProvider(extra, vvv)


class InitialValueExtraPropertyDelegateProvider<T>
private constructor(
    private val extra: ExtraPropertiesExtension,
    private val initialValue: T
) {
    companion object {
        fun <T> of(extra: ExtraPropertiesExtension, initialValue: T) =
            InitialValueExtraPropertyDelegateProvider(extra, initialValue)
    }

    operator fun provideDelegate(thisRef: Any? , property:kotlin.reflect.KProperty< * >): InitialValueExtraPropertyDelegate<T> {
        // Insert the variable name (KKK) as key
        extra.set(property.name, initialValue)
        return InitialValueExtraPropertyDelegate.of(extra)
        // InitialValueExtraPropertyDelegate(extra)}}class InitialValueExtraPropertyDelegate<T>
private constructor(
    private val extra: ExtraPropertiesExtension
) {
    companion object {
        fun <T> of(extra: ExtraPropertiesExtension) =
            InitialValueExtraPropertyDelegate<T>(extra)
    }

    // Assign. kkk = nvvv -> extra.set(kkk, nvvv)
    operator fun setValue(receiver: Any? , property:kotlin.reflect.KProperty<*>, value: T) =
        extra.set(property.name, value)

    // Value operation. val nk = kkk -> val nk = extra.get(kkk)
    @Suppress("unchecked_cast")
    operator fun getValue(receiver: Any? , property:kotlin.reflect.KProperty< * >): T =
        uncheckedCast(extra.get(property.name))
}
Copy the code

An honest commission. Val KKK: T by extra

operator fun ExtraPropertiesExtension.provideDelegate(receiver: Any? , property:KProperty< * >): MutablePropertyDelegate =
    if (property.returnType.isMarkedNullable) NullableExtraPropertyDelegate(this, property.name)
    else NonNullExtraPropertyDelegate(this, property.name)

private
class NonNullExtraPropertyDelegate(
    private val extra: ExtraPropertiesExtension,
    private val name: String
) : MutablePropertyDelegate {

    override fun <T> getValue(receiver: Any? , property:KProperty< * >): T =
        if(! extra.has(name)) cannotGetExtraProperty("does not exist")
        else uncheckedCast(extra.get(name) ? : cannotGetExtraProperty("is null"))

    override fun <T> setValue(receiver: Any? , property:KProperty<*>, value: T) =
        extra.set(property.name, value)

    private
    fun cannotGetExtraProperty(reason: String): Nothing =
        throw InvalidUserCodeException("Cannot get non-null extra property '$name' as it $reason")}Copy the code

GetValue, setValue is a type conversion based on the variable type. So you have to write the type, and you have to write it right.

buildSrc

The Kotlin DSL Plugin documentation

Another way to accomplish sharing. Create the buildSrc folder in the rootProject directory and create build.gradle.kts.

/
|-buildSrc
  |- src/main/kotlin/xxx.kt
  |- build.gradle.kts
Copy the code
//buildSrc/build.gradle.kts
plugins {
    `kotlin-dsl`
}

repositories {
    mavenCentral()
}
Copy the code

Content under SRC /main/kotlin is shared within the project. So we can define the variable here:

// src/main/kotlin/versions.kt
const val kotlinVersion = "1.5.30".Copy the code

You can use it anywhere else.

The advantage is that adding additional functionality to Gradle is easier and easier to manage.

The downside is that if variables are placed here, the IDE visual Project Structure fails to recognize them and will always indicate that there is something to update.

The module management

There’s nothing to talk about. New a module. Select the type as required. Then there’s build.gradle.kts to handle dependencies and builds. Include in the Settings. Gradle. KTS.

When A_module depends on B_module:

// A_module build.gradle.kts
dependencies {
    implementation(project(":B_module"))
Copy the code

See the documentation for more details. I switched to KTS to make the document easier to read.