Pretty Gradle — Android [TR]

Talha Fakıoğlu
Kotlin Türkiye
Published in
6 min readJan 30, 2024

Bu yazımızda projemizde gradle’ı nasıl daha temiz kullanabiliriz? bu konuya değineceğiz. Gradle’ı daha efektif ve temiz kullanabilmek için dikkat edeceğimiz maddelerimiz;

  • Convention Plugin Nedir?
  • Convention Plugin Kullanımı
  • Type-safe Project Accessors Nedir?
  • Type-safe Project Accessors Kullanımı

Gradle Nedir?

Gradle, açık kaynaklı bir proje yönetim aracıdır ve genellikle JVM (Java Virtual Machine) tabanlı projelerde kullanılır. Android Studio, Android uygulama geliştirmek için popüler bir IDE (Integrated Development Environment) olduğundan, Android projelerinde Gradle sıkça kullanılır.

Gradle, proje yapılandırmasını tanımlamak ve yönetmek için DSL (Domain Specific Language) tabanlı bir dil kullanır. Bu, geliştiricilere projenin nasıl derleneceği, hangi bağımlılıkların kullanılacağı ve diğer konfigürasyonları belirtmek için bir yol sağlar.

Bazı temel Gradle dosyaları şunlardır:

  1. build.gradle (Project Level): Bu dosya, genellikle projenin kök dizininde bulunur ve proje düzeyindeki yapılandırmaları içerir.
  2. build.gradle (Module Level): Bu dosya, genellikle bir uygulama modülünün içinde bulunur ve bu modülle ilgili yapılandırmaları içerir. Örneğin, uygulamanın bağımlılıkları, derleme sürümü vb.

Gradle, modüler bir yapıya izin verir ve farklı modüller arasında bağımlılıkları yönetmek için kullanılır. Ayrıca, farklı derleme görevlerini ve diğer geliştirme görevlerini tanımlamak için kullanılabilir. Gradle, Android uygulamalarının geliştirilmesini daha düzenli ve yönetilebilir hale getirmek için güçlü bir araçtır.

Android Projelerinde Kullanılan Gradle Örneği

Projemizi ilk oluşturduğumuzda göreceğimiz gradle’ın ilk hali bu şekilde olacaktır.

Peki biz birden fazla modül kullanmak istediğimiz zaman her modül’de bu şekilde hemen hemen yanı kod blokları mı olacak? Bu ortak kod bloklarını tek bir yerde toplayıp her modülümüzün gradle’ında kullansak nasıl olur? Daha okunabilir, aradığımız, değişiklik yapmak istediğimiz blok’u tek bir yerden düzenleyip kullansak iyi olurdu sanki. Bunu nasıl yapabiliriz ona bakalım.

Convention Plugin Nedir?

Convention plugin, Gradle’da ortak yapı yapılandırmasını kapsüllemenin ve yeniden kullanmanın bir yoludur. Geliştiricilerin bir proje veya modül için bir dizi kural tanımlamasına ve ardından bu kuralları diğer projelere veya modüllere uygulamasına olanak tanır. Bu, yapı yönetimini büyük ölçüde basitleştirebilir ve verimliliği artırabilir.

Convention plugin, Gradle’da ortak kullanılan yapıları, blokları bir araya toplayıp, yeniden kullanmamıza yarayan bir yoldur. Biz geliştiricilerin projemizde bulunan modülleri daha iyi yönetmemize, bakımı kolay hale getirmeye yarar.

Convention plugin’inin sağladığı bazı avantajlar;

  • Gradle yönetimini büyük ölçüde basitleştirir ve verimliliği arttırır.
  • Aynı kod bloklarını kullanan modüller için kod tekrarının önüne geçer.
  • Build configuration bakımını kolaylaştırır.

Nasıl Kullanılır?

Convention Plugin’i kullanmak için ilk önce Android Studio’da android görünümünden project görünümüne geçiyoruz. Burada görselde olduğu gibi sağ tıklayıp yeni bir module oluşturuyoruz.

Açılan ekranda modülümüzün ismini build-logic olarak isimlendiriyoruz. Modülümüz oluştuktan sonra içerisine convention ismi ile bir modül daha oluşturuyoruz. Daha sonra görseldeki gibi build-logic üzerine gelip sağ tıklıyoruz ve yeni bir dosya oluşturup ismine de settings.gradle.kts diyoruz.

settings.gradle.kts dosyamız oluştuktan sonra içerisine aşağıdaki kod blokunu ekliyoruz.

dependencyResolutionManagement {
repositories {
google()
mavenCentral()
}
versionCatalogs {
create("libs") {
from(files("../gradle/libs.versions.toml"))
}
}
}

rootProject.name = "build-logic"
include(":convention")

Şimdi sırası ile convention plugin’lerimizi oluşturabiliriz. Build-logic modülümüz içerisindeki convention modülümüze gelip src klasörü altından new -> Kotlin Class/File diyerek aşağıdaki gibi convention plugin’lerimizi oluşturuyoruz.

İlk önce kotlin konfigürasyonları için KotlinAndroid adında class oluşturuyorum ve aşağıdaki gibi konfigürasyonlarımı ekliyorum.

internal fun Project.configureKotlinAndroid(
commonExtension: CommonExtension<*, *, *, *, *>,
) {
commonExtension.apply {
compileSdk = 34

defaultConfig {
minSdk = 24

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
}
}

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

internal fun Project.configureKotlinJvm() {
extensions.configure<JavaPluginExtension> {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

configureKotlin()
}

private fun Project.configureKotlin() {
tasks.withType<KotlinCompile>().configureEach {
kotlinOptions {
jvmTarget = JavaVersion.VERSION_17.toString()
// Treat all Kotlin warnings as errors (disabled by default)
// Override by setting warningsAsErrors=true in your ~/.gradle/gradle.properties
val warningsAsErrors: String? by project
allWarningsAsErrors = warningsAsErrors.toBoolean()
freeCompilerArgs = freeCompilerArgs + listOf(
"-opt-in=kotlin.RequiresOptIn",
// Enable experimental coroutines APIs, including Flow
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
"-opt-in=kotlinx.coroutines.FlowPreview",
)
}
}
}

Daha sonrasında ise buildFeatures, composeOptions gibi konfigürasyonlar için ise AndroidCompose adında bir class oluşturuyorum ve aşağıdaki kod bloklarımı ekliyorum.

internal fun Project.configureAndroidCompose(
commonExtension: CommonExtension<*, *, *, *, *>,
) {
commonExtension.apply {
buildFeatures {
viewBinding = true
dataBinding {
this.enable = true
}
compose = true
}

composeOptions {
kotlinCompilerExtensionVersion = "1.5.4"
}
}

configureKotlinAndroid(commonExtension)
}

Böylelikle her gradle içerisinde kullanılabilecek konfigürasyonlarımı tek bir yere toplamış oldum ve ileride yeni modüller eklediğim taktirde aynı kod bloklarını tekrar tekrar yazmama gerek kalmadan tek bir yerden kullanıp, düzenleyebileceğim.

Note: Eğer projenizde version catalog kullandıysanız, bu extension’ı eklemeniz gerekiyor.

val Project.libs
get(): VersionCatalog = extensions.getByType<VersionCatalogsExtension>().named("libs")

Version Catalog ile ilgili yazıma da linkten ulaşabilirsiniz.

Eklediğim konfigürasyonları kullanabileceğim bir convention plugin oluşturacağım. Bu convention plugin’i AndroidApplicationConventionPlugin ile isimlendiriyorum ve aşağıdaki gibi kod bloklarımı ve eklediğim konfigürasyonları ekliyorum.

class AndroidApplicationConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
with(pluginManager) {
apply("com.android.application")
apply("org.jetbrains.kotlin.android")
}

extensions.configure<ApplicationExtension> {
configureAndroidCompose(this)
configureBuildTypes(this)
}
}
}

}

Application modülümde kullanacağım convention plugin’imi bu şekilde oluşturmuş oldum. Bu convention plugin’i kullanmak için build-logic modülüm içerisindeki convention build.gradle.kts içerisini aşağıdaki gibi düzenliyorum.

plugins {
`kotlin-dsl`
}

// Configure the build-logic plugins to target JDK 17
// This matches the JDK used to build the project, and is not related to what is running on device.
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
tasks.withType<KotlinCompile>().configureEach {
kotlinOptions {
jvmTarget = JavaVersion.VERSION_17.toString()
}
}

dependencies {
compileOnly(libs.android.gradlePlugin)
compileOnly(libs.kotlin.gradlePlugin)
}

gradlePlugin {
plugins {
register("androidApp") {
id = "pg.application" // whatever you want
implementationClass = "AndroidApplicationConventionPlugin"
}
}
}

Oluşturmuş olduğum convention plugin’imi plugins blokunda register ederek projemin içerisindeki modüllerde kullanılmaya hazır hale getirmiş oluyorum.

app modülümün içerisinde gereksiz kodları silip, convention plugin’imi kullanabilirim. Kullanmam için tek yapmam gereken plugin’i vermiş olduğum id ile çağırmak.

plugins {
id("pg.application")
}

android {
namespace = "com.tfaki.prettygradle"

defaultConfig {
applicationId = "com.tfaki.prettygradle"
targetSdk = 34
versionCode = 1
versionName = "1.0"
}

packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
}

İşte app modülümün içerisinin son hali bu şekilde oldu. Bir başka modül oluşturduğumda bu defa library modül için oluşturduğum convention plugin’imi çağırıp kullanabilirim.

Mesela burda hilt kullanmak istediğimde de oluşturmam gereken convention plugin şu şekilde olacak;

class AndroidHiltConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
with(pluginManager){
apply("dagger.hilt.android.plugin")
apply("com.google.devtools.ksp")
}

dependencies {
"implementation"(libs.findLibrary("hilt").get())
"ksp"(libs.findLibrary("hilt.compiler").get())
}
}
}
}

Ve bunu yine build-logic:convention build.gradle.kts içerisinde register ederek kullanabilirim.

gradlePlugin {
plugins {
register("androidApp") {
id = "pg.application"
implementationClass = "AndroidApplicationConventionPlugin"
}
register("androidHilt") {
id = "pg.hilt"
implementationClass = "AndroidHiltConventionPlugin"
}
}
}

App modülümün içerisinde yapmam gereken tek şey id ile plugin’imi çağırmak;

plugins {
id("pg.application")
id("pg.hilt")
}

İşte convention plugin için tek yapmam gereken şey bu. Ve diğer modüllerimde de hilt kullanacağım zaman sadece id kullanmam yeterli olacak.

Birden fazla convention plugin örneği incelemek için github’ımdaki repoyu inceleyebilirsiniz.

Basit kullanım için örneği görmek için bu linkteki github’daki repomu inceleyebilirsiniz.

Peki biz gradle’ımızı daha iyi hale getirebilir miyiz? Bunun cevabı tabikide evet. Bu konuda da bize Type-safe yardımcı oluyor.

Type-safe Project Accessors Nedir?

Modülerleştirilmiş bir Android projesi geliştirirken, birçok modülün olması ve bunların bazılarının birbirine bağımlı olması beklenir. Ancak proje büyüdükçe modüllerin konumlarını ve adlarını takip etmek giderek zorlaşır, bu da kişinin bu bilgiyi hatırlamak için biraz zaman harcaması gereken durumlara ve hatta derleme hatalarına yol açabilir.

dependencies {
implementation(project(":domain"))
implementation(project(":data"))
implementation(project(":common-ui"))
implementation(project(":upcoming"))
implementation(project(":nowplaying"))
implementation(project(":popular"))
}

Neyse ki, Android Studio 2020.3.1 Artic Fox ile Gradle 7.0'da, hayatı kolaylaştırmak için yeni bir tür güvenli proje erişimcileri dahil edildi.

Type-safe Project Accessors Kullanımı

Bu özelliğimizin kullanılması için gerekli kurulumlarımız çok kolay. Sadece tek bir satırda tüm gereksinimlerimizi karşılayabiliyoruz.

Projemiz içerisinde bulunan settings.gradle.kts dosyamıza bu bir satırı ekliyoruz;

enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")

İşte tüm yapmamız gereken bu kadar. Şimdi gradle’ımız içerisinde modüllerimizi depend ettiğimiz kısımları düzenliyoruz.

dependencies {
implementation(projects.domain)
implementation(projects.data)
implementation(projects.commonUi)
implementation(projects.upcoming)
implementation(projects.nowplaying)
implementation(projects.popular)
}

Böylelikle gradle’ımız hem daha güzel duruyor ve derleme hatalarınında önüne geçmiş oluyoruz.

Umarım yararlı bir yazı olmuştur. Bir sonraki yazıda görüşmek üzere 👋

--

--