รวมมิตรอัพเดตแพชครั้งใหญ่ กับ Kotlin DSL กับ toml
เราได้มีโอกาสได้ช่วยทำแอพรวมมิจ ที่เป็นข่าวตามสื่อต่าง ๆ ที่มี 9arm เป็นต้นคิด และพี่เอก Android GDE เป็นคน implement หลัก
แต่เนื่องจากโปรเจกต์นี้ feature หลักได้มีการ implement ไปแล้ว เราเลยหยิบ feature Open Source Licenses ที่ทำง่ายสุดแล้วนั้น
แต่ด้วยความที่เป็นโปรเจกต์ใหม่ ตัว UI ทำด้วย Jetpack Compose ด้วย แล้วมีการ setting dependency แบบใหม่ ที่เราเองก็ทำโปรเจกต์ที่มีอายุพอสมควร ยังไม่ได้ใช้ของใหม่หมด เลยต้องมาเรียนรู้กับเรื่อง Kotlin DSL โอเคมันแค่สลับตำแหน่งจากของเดิม แต่ toml นี่สินะ สิ่งที่ต้องเรียนรู้ใหม่ในครั้งนี้
ในบล็อกนี้เลยมาเล่าการใช้งาน toml กับ Kotlin DSL กัน ว่ามันแตกต่างจากเดิมยังไง โดยอธิบายผ่านโปรเจกต์รวมมิจนี่แหละ
ทำความรู้จัก Gradle กันก่อน
ในการ build โปรเจกต์ Android application นั้น เราจะมี gradle ในการช่วยจัดการการ build ให้ง่ายขึ้น ซึ่งมันเป็น build system file ที่เราสามารถเขียนโค้ดกำหนดรูปแบบในการ build app รวมถึงแบ่งการทำงานข้างในแต่ละขั้นตอน หรือเรียกว่า task เพื่อแยกส่วนการทำงานต่าง ๆ ออกจากกัน
เดิมทีไฟล์ gradle ซึ่งไฟล์ที่ว่าคือ build.gradle
มีหน้าที่ควบคุมการทำงานในส่วนต่าง ๆ ของโปรเจกต์ เป็นภาษา groovy
ไฟล์หลัก ๆ ที่เราใช้กัน
build.gradle ของ module
เช่น app/build.gradle
- ประกาศ plugins ที่ใช้ในจัดเตรียม library และ infrastructure ที่จำเป็นในโปรเจกต์นั้น ๆ
// build app ได้
apply plugin: 'com.android.application'
// ใช้ภาษา Kotlin ในโปรเจกต์เราได้
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
- กำหนด Android property หรือค่าต่าง ๆ ที่กำหนดลงโปรเจกต์แอพของเรา เพื่อทำให้สามารถทำงานต่าง ๆ บน Android ได้
android {
compileSdkVersion 34
buildToolsVersion "34.0.0"
defaultConfig {
applicationId "com.example.sample"
minSdkVersion 23
targetSdkVersion 34
}
}
- จัดการ dependency ต่าง ๆ โดยการประกาศเรียกใช้งาน library
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.2.1'
...
}
build.gradle ของโปรเจกต์
- เชื่อมต่อกับ repository ซึ่งเป็นที่เก็บ library ต่าง ๆเพื่อ download code ลงมาเก็บลงบนเครื่อง
repositories {
google()
mavenCentral()
}
setting.gradle
เป็นไฟล์ที่บอกว่าโปรเจกต์นี้เราจะใช้ module ใดบ้าง ปกติจะแค่นี้เนอะ
include ':app'
ทำไมต้องเปลี่ยนมาเป็น Kotlin DSL
เนื่องจาก Android Gradle plugin 4.0 นั้น support ภาษา Kotlin ที่เรารัก ซึ่งใช้แทนภาษา groovy เพราะว่าพอเป็น Kotlin แล้วมันอ่านง่ายกว่า พวกกับได้ compile-time checking ที่ดีขึ้น รวมถึง IDE ก็ support ด้วย
ตั้งแต่ Android Studio Giraffe โปรเจกต์ใหม่จะถูกบังคับให้มาใช้ แบบ Kotlin DSL แทน เป็น build.gradle.kts
ซึ่งถ้าโปรเจกต์ที่เรามีอยู่จะย้ายตาม ต้องมีการปรับบางส่วนเพื่อให้ support ตัว Kotlin DSL นี้
ทำความรู้จัก syntax ของ Kotlin DSL แบบคร่าว ๆ
เริ่มกันที่ syntax ก่อน เอาแบบคร่าว ๆ
- ก่อนอื่น ไฟล์เพิ่ม
.kts
ต่อท้าย จะมีbuild.gradle.kts
และsetting.gradle.kts
- เพิ่ม parent ให้กับ method ที่เรียก โดยมีวงเล็บครอบเหมือนเรียก function
// build.gradle
compileSdkVersion 30
// build.gradle.kts
compileSdkVersion(30)
- ใช้
=
ในการ assign ค่า
// build.gradle
java {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
// build.gradle.kts
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
- ใช้
"
ในการ quote string และใช้คุณสมบัติของ Kotlin อย่าง$
ในการเรียกค่าจากตัวแปรนั้น ๆ
// build.gradle
myRootDirectory = "$project.rootDir/tools/proguard-rules-debug.pro"
// build.gradle.kts
myRootDirectory = "${project.rootDir}/tools/proguard-rules-debug.pro"
- เดิมใช้
def
ตอนนี้ใช้var
กับval
ในการประกาศตัวแปร
// build.gradle
myRootDirectory = "$project.rootDir/tools/proguard-rules-debug.pro"
// build.gradle.kts
myRootDirectory = "${project.rootDir}/tools/proguard-rules-debug.pro"
- ตัวแปรที่เป็น boolean properties มี is นำหน้า
// build.gradle
android {
buildTypes {
release {
minifyEnabled true
shrinkResources true
...
}
debug {
debuggable true
...
}
...
// build.gradle.kts
android {
buildTypes {
getByName("release") {
isMinifyEnabled = true
isShrinkResources = true
...
}
getByName("debug") {
isDebuggable = true
...
}
...
- ใช้ list กับ map
// build.gradle
jvmOptions += ["-Xms4000m", "-Xmx4000m", "-XX:+HeapDumpOnOutOfMemoryError</code>"]
// build.gradle.kts
jvmOptions += listOf("-Xms4000m", "-Xmx4000m", "-XX:+HeapDumpOnOutOfMemoryError")
// build.gradle
def myMap = [key1: 'value1', key2: 'value2']
// build.gradle.kts
val myMap = mapOf("key1" to "value1", "key2" to "value2")
มาลองกับไฟล์จริงกัน
หลังจากทำความเข้าใจกันสักพัก มาที่ไฟล์จริงกันบ้าง
พวกที่เชื่อมต่อกับ repository ย้ายไป setting.gradle.kts
ส่วนที่เรียก module ต่าง ๆ ยังเหมือนเดิม
// setting.gragle.kts
pluginManagement {
repositories {
google {
mavenCentral()
gradlePluginPortal()
}
}
include(":app")
ในส่วน build.gradle.kts
ของโปรเจกต์ จะมีแค่ plugin ที่ใช้ในโปรเจกต์
ถ้าอันไหนไม่อยากใช้ที่ root project ให้ใส่ apply false
ไป
// build.gragle.kts (project)
plugins {
id("com.android.application") version "8.1.0" apply false
id("org.jetbrains.kotlin.android") version "1.8.10" apply false
...
}
และ build.gradle.kts
ของ module มี set plugin ที่ล้อกับโปรเจกต์
// build.gragle.kts (project)
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
...
}
พวกกำหนด Android property และ dependency ยังคงเหมือนเดิม แค่เปลี่ยน syntax เฉย ๆ นะ
แล้ว toml คืออะไร?
ฉันก็ไม่รู้เหมือนกัน55555555 หยอก ๆ
toml ย่อมาจาก Tom's Obvious, Minimal Language เป็นรูปแบบไฟล์ที่ใช้สำหรับการจัดเก็บข้อมูลโครงสร้าง เหมาะอย่างยิ่งสำหรับการกำหนดค่าและการจัดการ dependencies โดยออกแบบมาให้อ่านง่าย เขียนง่าย ยืดหยุ่น และมีประสิทธิภาพ จึงเหมาะกับการจัดการ dependency และการตั้งค่า config ต่าง ๆ ในโปรเจกต์
สำหรับการนำมาใช้ในโปรเจกต์ Android จะเป็นไฟล์ชื่อว่า libs.version.toml
ข้างในไฟล์จะแบ่งเป็น 3 ส่วน แบบนี้
[versions]
...
[libraries]
...
[plugins]
...
- version: เก็บเลข version ทั้งของ library และ plugin ไว้ที่นี่
- libraries: ใส่ dependency ที่เราใช้
- plugins: ใส่ plugin ที่เราใช้ในโปรเจกต์
นำ Kotlin DSL และ toml มาประกอบร่าง
และนี่คือสิ่งที่เห็นในโปรเจกต์รวมมิจ ที่มีทั้ง Kotlin DSL และ toml
แล้วเราจะเพิ่มตัว open source notice เข้าไปยังไงนะ
เริ่มต้นที่ libs.version.toml
เลย เราต้องใส่ตัว oss ทั้งที่ plugin และ library เลย หน้าตาจะเป็นแบบนี้
[versions]
...
ossLicensesPlugin = "0.10.6"
oss = "17.0.1"
[libraries]
...
play-oss-licenses = { module = "com.google.android.gms:play-services-oss-licenses", version.ref = "oss" }
[plugins]
...
ossLicenses = { id = "com.google.android.gms.oss-licenses-plugin", version.ref = "ossLicensesPlugin" }
- version: ใส่เลข version ของตัว oss โดยของ plugin คือ 0.10.6 และ library คือ 17.0.1
- libraries: dependency เป็น
com.google.android.gms:play-services-oss-licenses:17.0.0
ดังนั้นเราจะแยกเป็น module ของ dependency เป็นcom.google.android.gms:play-services-oss-licenses
และ version เป็นoss
- plugins: ตัว classpath มันเป็น
classpath 'com.google.android.gms:oss-licenses-plugin:0.10.6'
และเรียกใช้ plugin เป็นcom.google.android.gms.oss-licenses-plugin
ซึ่ง syntax ที่ถูกต้องเป็นตัวที่ใช้ใน plugin มี module เป็นcom.google.android.gms.oss-licenses-plugin
และ version เป็นossLicensesPlugin
จุดนี้ที่เกิดปัญหา มาอ่านกันต่อ
จากนั้นเอาสิ่งที่เรา set จาก toml ไปใช้ต่อที่แรก คือ build.gradle.kts
ของ module เพื่อเรียกใช้ dependency กัน
dependencies {
...
implementation(libs.play.oss.licenses)
}
และไปใช้ plugin ที่ build.gradle.kts
ของโปรเจกต์
dependencies {
...
alias(libs.plugins.ossLicenses) apply false
}
แต่ของ plugin จะเกิดปัญหา error นี้ขึ้นมา
พอเราไม่เรียกใช้ plugin ของ oss ทำให้พอเราเรียกเปิดหน้า open source notice มาทำงานไม่ถูกต้อง
วิธีแก้เราหานานมากกกกกกก จนมาเจอวิธีจาก stackoverflow ว่าให้ไป config resolutionStrategy
ที่ settings.gradle.kts
แบบนี้
pluginManagement {
repositories {
google()
}
// อันที่เพิ่ม
resolutionStrategy {
eachPlugin {
if (requested.id.id == "com.google.android.gms.oss-licenses-plugin") {
useModule("com.google.android.gms:oss-licenses-plugin:${requested.version}")
}
}
}
}
พอแก้ปุ๊ปหายดีเรียบร้อย ทำงานได้ถูกต้อง เย้ ๆ
ทั้งหมดที่เราเรียนรู้ก็จะประมาณนี้แหละทุกคน คิดว่าน่าจะเป็นประโยชน์กับคนที่เจอ setting ใหม่แบบนี้ แล้วตกอกตกใจเช่นเรา
Reference
ติดตามข่าวสารตามช่องทางต่าง ๆ และทุกช่องทางโดเนทกันไว้ที่นี่เลย แนะนำให้ใช้ tipme เน้อ ผ่าน promptpay ได้เต็มไม่หักจ้า
ติดตามข่าวสารแบบไว ๆ มาที่ Twitter เลย บางอย่างไม่มีในบล็อก และหน้าเพจนะ