gradle kotlin

If you are scared of Gradle scripts with unfamiliar groovy syntax files, then Kotlin DSL is made for you. Its time to face the Gradle scripts with more confidence. In this article, we will convert some common Groovy-based Gradle scripts into Kotlin DSL scripts.

如果您害怕带有陌生的常规语法文件的Gradle脚本,那么Kotlin DSL就是为您量身定做的。 是时候更加自信地面对Gradle脚本了。 在本文中,我们将一些基于Groovy的通用Gradle脚本转换为Kotlin DSL脚本。

让我们开始吧 (Let’s get started)

Open half baked side android project 😉 of yours. In your project-level root folder create a folder named buildSrc same as that of your app/ folder in your project. This is a special folder to enable the Gradle scripts at the project level. Hit the sync button and you will see how these changes and adds some temporary files inside it.

打开您的半烘焙Android项目project。 在项目级别的根文件夹中,创建一个名为buildSrc的文件夹,该文件夹与项目中的app /文件夹相同。 这是一个特殊的文件夹,用于在项目级别启用Gradle脚本。 点击同步按钮,您将看到这些变化并在其中添加了一些临时文件。

Image for post
buildSrc folder
buildSrc文件夹

Half the battle is done 😀 now in order to enable the Kotlin DSL we gotta do something more here. So put our lazy brain to work and create a file inside buildSrc naming it build.gradle.kts open now the newly created file to add some code that tells Gradle to enable the Kotlin-DSL plugin for our project.

现在已经完成了一半的工作-为了启用Kotlin DSL,我们必须在这里做更多的事情。 因此,让我们懒惰的大脑工作并在buildSrc内创建一个文件,将其命名为build.gradle.kts。现在打开新创建的文件,添加一些代码,告诉Gradle为我们的项目启用Kotlin-DSL插件。

build.gradle.kts
build.gradle.kts

Sync now, That’s it Battle is won, and Kotlin DSL is enabled in our project.

现在同步,就这样,Battle赢了,并且在我们的项目中启用了Kotlin DSL。

还有什么要做的吗? 🤔 (Is there anything left to do? 🤔)

Yes, whatever we have done is of no use until we put Kotlin DSL into some action.

是的,在我们将Kotlin DSL付诸行动之前,我们所做的一切都没有用。

 Managing dependencies in large android projects is still cumbersome and hectic and that unfamiliar groovy syntax makes it even harder to understand the changes.

Kotlin DSL brings the simplicity of the Kotlin language syntax and rich API set right into the script files on top of that code completion makes it perfect to work with Gradle script files. We will use this to manage our dependencies and project settings configurations more elegantly. Kotlin DSL files have extension as .kts so we will be converting all of our .gradle files to .gradle.kts

Kotlin DSL将Kotlin语言语法的简单性和丰富的API设置直接添加到脚本文件中,并且代码完成使它非常适合与Gradle脚本文件一起使用。 我们将使用它来更优雅地管理我们的依赖项和项目设置配置。 Kotlin DSL文件的扩展名为.kts,因此我们将所有.gradle文件转换为.gradle.kts

Before converting our files to Kotlin DSL let’s have some necessary files in place in order to go ahead. Create folders inside the buildSrc folder as below and create the stated files.

在将文件转换为Kotlin DSL之前,让我们准备一些必要的文件以继续进行。 在buildSrc文件夹内创建文件夹,如下所示,并创建声明的文件。

buildSrc->src->main->java

AppConfig.kt: (AppConfig.kt:)

//app level config constants
object AppConfig {
    const val compileSdk = 30
    const val minSdk = 21
    const val targetSdk = 30
    const val versionCode = 1
    const val versionName = "1.0.0"
    const val buildToolsVersion = "29.0.3"


    const val androidTestInstrumentation = "androidx.test.runner.AndroidJUnitRunner"
    const val proguardConsumerRules =  "consumer-rules.pro"
    const val dimension = "environment"
}

This file helps us manage our app-level configurations related to the project at once place.

此文件可帮助我们立即管理与该项目相关的应用程序级配置。

Versions.kt (Versions.kt)

//version constants for the Kotlin DSL dependencies
object Versions {
    //app level
    const val gradle = "4.0.1"
    const val kotlin = "1.4.0"


    //libs
    val coreKtx = "1.2.0"
    val appcompat = "1.3.0-alpha01"
    val constraintLayout = "2.0.0-beta8"


    //test
    val junit = "4.12"
    val extJunit = "1.1.1"
    val espresso = "3.2.0"
}

This file helps us separate our versioning of the libraries in one place.

此文件可帮助我们将库的版本控制放在一个地方。

AppDependencies.kt (AppDependencies.kt)

import org.gradle.api.artifacts.dsl.DependencyHandler


object AppDependencies {
    //std lib
    val kotlinStdLib = "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${Versions.kotlin}"


    //android ui
    private val appcompat = "androidx.appcompat:appcompat:${Versions.appcompat}"
    private val coreKtx = "androidx.core:core-ktx:${Versions.coreKtx}"
    private val constraintLayout =
        "androidx.constraintlayout:constraintlayout:${Versions.constraintLayout}"


    //test libs
    private val junit = "junit:junit:${Versions.junit}"
    private val extJUnit = "androidx.test.ext:junit:${Versions.extJunit}"
    private val espressoCore = "androidx.test.espresso:espresso-core:${Versions.espresso}"


    val appLibraries = arrayListOf<String>().apply {
        add(kotlinStdLib)
        add(coreKtx)
        add(appcompat)
        add(constraintLayout)
    }


    val androidTestLibraries = arrayListOf<String>().apply {
        add(extJUnit)
        add(espressoCore)
    }


    val testLibraries = arrayListOf<String>().apply {
        add(junit)
    }
}


//util functions for adding the different type dependencies from build.gradle file
fun DependencyHandler.kapt(list: List<String>) {
    list.forEach { dependency ->
        add("kapt", dependency)
    }
}


fun DependencyHandler.implementation(list: List<String>) {
    list.forEach { dependency ->
        add("implementation", dependency)
    }
}


fun DependencyHandler.androidTestImplementation(list: List<String>) {
    list.forEach { dependency ->
        add("androidTestImplementation", dependency)
    }
}


fun DependencyHandler.testImplementation(list: List<String>) {
    list.forEach { dependency ->
        add("testImplementation", dependency)
    }
}

This file holds all the dependencies of our app related to UI, test, and other third-party libraries. Apart from that this also holds the Kotlin extension functions for implementation, testImplementation, androidTestImplementation, kapt which basically now accepts a list of String (dependencies) which is helpful for adding multiple dependencies at once instead of adding one by one in build.gradle file. You can play around and separate dependencies based on the module name also by creating a different list of dependencies for your module and add all at once by using a single line of code Gradle script.

该文件包含我们应用程序与UI,测试和其他第三方库相关的所有依赖项。 除此之外,它还保留了Kotlin扩展函数,用于实现testImplementationandroidTestImplementationkapt ,它现在基本上接受一个String(依赖项)列表,这有助于一次添加多个依赖项,而不是在build.gradle文件中一一添加。 您还可以通过为模块创建不同的依赖关系列表,并根据模块名称来查找和分离依赖关系,并使用一行代码Gradle脚本一次添加所有依赖关系。

Here is how it will look like when we are done adding all the Kotlin files.

添加完所有Kotlin文件后,这就是它的样子。

Image for post

Also, it’s no hard and fast rule we can also manage all these constants in one file also but if your project is large enough to make you go mad for a single change then it’s better to have separate the concerns in different files for different purposes. Let’s convert

同样,我们也可以在一个文件中管理所有这些常量不是一成不变的规则,但是如果您的项目足够大,足以使您发疯进行单个更改,那么最好将关注点分离在不同文件中以达到不同目的。 让我们转换

settings.gradle (settings.gradle)

Our existing code for settings.gradle file as below

我们现有的settings.gradle文件代码如下

include ':app:repository'
include ':app:core'
include ':app'
rootProject.name = "Hilt Android"

settings.gradle.kts (settings.gradle.kts)

include(":repository", ":core", ":app")
rootProject.name = "Hilt Android"

include is now just a function taking vararg of String to include the modules in the project. Moving next we gonna change our project level build.gradle file

include现在只是一个将String的vararg包含在项目中的模块的函数。 接下来,我们将更改项目级别的build.gradle文件

build.gradle (build.gradle)

// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
    ext.kotlin_version = "1.3.72"
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath "com.android.tools.build:gradle:4.0.1"
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"


        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}


allprojects {
    repositories {
        google()
        jcenter()
    }
}


task clean(type: Delete) {
    delete rootProject.buildDir
}

build.gradle.kts (build.gradle.kts)

// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath("com.android.tools.build:gradle:${Versions.gradle}")
        classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlin}")
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}


allprojects {
    repositories {
        google()
        jcenter()
    }
}


tasks.register("clean", Delete::class) {
    delete(rootProject.buildDir)
}

Kotlin DSL version is almost the same since it’s using DSL to map those syntaxes and tries to be as close as possible for example.

Kotlin DSL版本几乎相同,因为它使用DSL映射这些语法并尝试尽可能地接近。

classpath("com.android.tools.build:gradle:${Versions.gradle}")

classpath is just a regular Kotlin function taking String as input. Which we all know and understand and can relate the syntax. Let’s see how our main app-level build.gradle file changes to build.gradle.kts

classpath只是将String作为输入的常规Kotlin函数。 我们都知道和理解并且可以关联语法。 让我们看看我们的主要应用程序级别的build.gradle文件如何更改为build.gradle.kts

Plugins are the first thing in the main app-level Gradle file which enables the android support in regular Intellij project or Kotlin support in an android project or any third party plugin which needed at module level can be applied in this file, we will see show that changes

插件是主应用程序级别Gradle文件中的第一件事,该文件可启用常规Intellij项目中的android支持或android项目中的Kotlin支持,或者可以在此文件中应用模块级别所需的任何第三方插件,我们将看到show改变

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

will be changed to

将更改为

plugins {id("com.android.application")kotlin("android")kotlin("android.extensions")}

or you can use another way of adding plugins as shown below, the above one is the DSL way of doing things.

或者您可以使用另一种添加插件的方式,如下所示,上面的一种是DSL的处理方式。

apply(plugin = "com.google.firebase.crashlytics")

now let’s try to go chunk by chunk for an android block with the basic setup.

现在,让我们尝试使用基本设置逐块处理android块。

android {compileSdkVersion 30
buildToolsVersion "29.0.3"defaultConfig {applicationId "com.vikas.hiltandroid"minSdkVersion 21
targetSdkVersion 30
versionCode 1
versionName "1.0"testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
//......
}

will be changed to

将更改为

android {compileSdkVersion(AppConfig.compileSdk)
buildToolsVersion(AppConfig.buildToolsVersion)
defaultConfig {applicationId = "com.vikas.hiltandroid"minSdkVersion(AppConfig.minSdk)
targetSdkVersion(AppConfig.targetSdk)versionCode = AppConfig.versionCodeversionName = AppConfig.versionNametestInstrumentationRunner = AppConfig.androidTestInstrumentation
}
//......
}

Now it’s possible to directly access the Kotlin object constants or any other constants like as shown above. Here we are using the earlier created constants in file AppConfig for the app level configurations. Next, let’s see how we can create the debug and release versions.

现在可以直接访问Kotlin 对象常数或任何其他常数,如上所示。 在这里,我们使用文件AppConfig中较早创建的常量进行应用程序级别配置。 接下来,让我们看看如何创建调试发行版本。

buildTypes {release {minifyEnabled falseproguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}

will be changed to

将更改为

buildTypes {getByName("release") {isMinifyEnabled = falseproguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),"proguard-rules.pro")}
}

you don’t have to create debug because it’s there by default until unless needed to change some properties. If so then do something like this.

您无需创建调试,因为默认情况下它一直存在,直到需要更改某些属性为止。 如果是这样,请执行以下操作。

getByName("debug") {isMinifyEnabled = false
}

flavors:

口味:

flavorDimensions(AppConfig.dimension)
productFlavors {create("staging") {applicationIdSuffix = ".staging"setDimension(AppConfig.dimension)}create("production") {setDimension(AppConfig.dimension)}
}

viewbinding:

视图绑定:

viewBinding {android.buildFeatures.viewBinding = true
}

coming to the main dependencies this is how it was like before

进入主要依赖关系这就是以前的样子

dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"implementation 'androidx.core:core-ktx:1.3.1'implementation 'androidx.appcompat:appcompat:1.1.0'implementation 'androidx.constraintlayout:constraintlayout:1.1.3'testImplementation 'junit:junit:4.12'androidTestImplementation 'androidx.test.ext:junit:1.1.1'androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'}

will be now changed to below

现在将更改为以下

dependencies {//std libimplementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))//app libsimplementation(AppDependencies.appLibraries)//test libstestImplementation(AppDependencies.testLibraries)
androidTestImplementation(AppDependencies.androidTestLibraries)}

this is quite maintainable, easy to read & understand.

这是相当容易维护,易于阅读和理解的。

putting all this together will look like as below.

将所有这些放在一起将如下所示。

plugins {
    id("com.android.application")
    kotlin("android")
    kotlin("android.extensions")
}


android {
    compileSdkVersion(AppConfig.compileSdk)
    buildToolsVersion(AppConfig.buildToolsVersion)


    defaultConfig {
        applicationId = "com.vikas.hiltandroid"
        minSdkVersion(AppConfig.minSdk)
        targetSdkVersion(AppConfig.targetSdk)
        versionCode = AppConfig.versionCode
        versionName = AppConfig.versionName


        testInstrumentationRunner = AppConfig.androidTestInstrumentation
    }


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


    flavorDimensions(AppConfig.dimension)
    productFlavors {
        create("staging") {
            applicationIdSuffix = ".staging"
            setDimension(AppConfig.dimension)
        }


        create("production") {
            setDimension(AppConfig.dimension)
        }
    }


    viewBinding {
        android.buildFeatures.viewBinding = true
    }


    packagingOptions {
        exclude("META-INF/notice.txt")
    }


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


dependencies {
    //std lib
    implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar"))))
    //app libs
    implementation(AppDependencies.appLibraries)
    //test libs
    testImplementation(AppDependencies.testLibraries)
    androidTestImplementation(AppDependencies.androidTestLibraries)
}

If you have any android module, conversion goes almost the same for the modules as well instead of using an android id plugin use library plugin id.

如果您有任何android模块,则模块的转换也几乎相同,而不是使用android id插件使用库插件id。

Kotlin DSL is not limited to the usages across Gradle scripts it’s way broader than just scripting. Let’s go and explore this side of Kotlin DSL by putting this into implementation.

Kotlin DSL不仅限于Gradle脚本的用法,它比脚本更广泛。 让我们将其付诸实施,探索Kotlin DSL的这一方面。

使用Kotlin DSL设置TextView的样式。 (Styling TextView using Kotlin DSL.)

Let’s start by creating the backbone which will support the DSL.

让我们从创建支持DSL的骨干开始。

object StyleDsl {


    fun styleWith(textView: TextView, block: TextView.() -> Unit): TextView {
        block(textView)
        return textView
    }


    inline fun TextView.applyFont(applyStyle: () -> Typeface) {
        typeface = applyStyle()
    }


    inline fun TextView.changeFontSize(fontStyle: () -> Int) {
        val size = fontStyle()
        textSize = size.toFloat()
    }


    inline fun TextView.applyColor(colorLambda: () -> Int) {
        setTextColor(colorLambda())
    }


}

in our MainActivity we will use this as follow.

在我们的MainActivity中,我们将按以下方式使用它。

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val tvTitle = findViewById<TextView>(R.id.tvTitle)
        //dsl applied 
        styleWith(tvTitle) {
            applyFont {
                Typeface.MONOSPACE
            }
            changeFontSize {
                34
            }
            applyColor {
                Color.DKGRAY
            }
        }
    }
}

输出: (Output:)

Image for post

This is the very basic example of Kotlin DSL, we can write to express almost anything using this. This is a much more clean and expressive way of doing programming. Since it uses Kotlin lambda expressions extensively to achieve this, we should be conscious of the extra classes it generates at the byte code level. So how to fix this 🤔, simply try to inline the lambda expressions where ever possible in order to reduce the extra class which would get generated for the small code blocks.

这是Kotlin DSL的非常基本的示例,我们可以使用它来表达几乎所有内容。 这是进行编程的一种更加干净和富有表现力的方式。 由于它广泛使用Kotlin lambda表达式来实现此目的,因此我们应该意识到它在字节码级别上生成的额外类。 因此,如何解决此问题,只需尝试尽可能内联lambda表达式,以减少为小代码块生成的额外类。

That’s it folks for today. Please be generous enough to comment for feedback or suggestions or any queries. Thanks for reading 😊.

今天就是这样。 请足够慷慨地对反馈或建议或任何疑问发表评论。 感谢您阅读😊。

参考文献/进一步阅读: (References/further read:)

翻译自: https://medium.com/@droidvikas/kotlin-dsl-gradle-scripts-in-android-made-easy-b8e2991e2ba

gradle kotlin

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐