4- Version Catalog Migration in QNB Android Mobile📢
Herkese Merhaba, ben Çağatay 🖐
QNB Mobil uygulamamızda Version Cataloga geçiş yaptık. Bu makale bir proje Version Catalog’a nasıl geçirilirden ziyade geçiş sırasında karşılaşılan zorluklar, sorunlar, geçişin avantajları, dezavantajlarını ve entegrasyon örneği ile birlikte tavsiyelerimizi sizlere aktarmaya çalıştığımız bir yazı olacak.
Android uygulamalarda Gradle dosyaları genellikle Groovy kullanılarak yazılmıştır. Ancak, Gradle’ın yeni özelliklerinden biri olan Version Catalog, proje bağımlılıklarını daha merkezi ve yönetilebilir bir şekilde tanımlamak için bizlere yeni bir yöntem sunmaktadır.
Zorluklar & Sorunlar 🛠
Gradle’ı groovy ile yazılmış büyük projelerde bu dosyalar ile ilgileniyor olmak, her seferinde sync istediği ve hata almaya çok müsait olduğu için açıkçası başlı başına yazılmış olan bir projeyi catloga geçirmek zorluk olarak değelendirebilir. Aşağıda belirteceğim sorunlar ve sürecin bir nebze daha kolay olması için bir tavsiye veriyor olacağım.
Geçiş yaparken karşılaşılan en yaygın sorun sync problemleridir. İlgili dosyaların Groovy’den Dsl ve sonrasında Version Catalog’a dönüştürülmesi sırasında Gradle’ın projeyi senkronize etme sürecinde çeşitli hatalar meydana gelir. Bu hatalar genellikle yanlış tanım, yanlış ve eksik bağımlılıklar veya uyumsuzluklardan kaynaklanır. Bu kısımda bir tavsiye vermek gerekirse projedeki gradleları adım adım version cataloga geçirip sync ederek ilerlemek gerekir. Gradle dosyalarında çalışmanın avantajlarından birisi bence bu olabilir. Bir gradle Groovy, birisi Version Catalog, diğeri DSL farklı bir tanesi Convention Plugin kullanılarak aynı anda sync edilip proje build alabilir. Süreç parçalı biçimde ilerlemelidir. Örnek verecek olursak x modulünün ilgili gradle geçişini tamamladıktan sonra projeyi sync edilip gerekirse build alınıp çalışırlığı kontrol edildikten sonra bir sonraki gradle refactor edilmeye başlanmalıdır. Bu sayede her adımda karşılaşılan sorunlar daha yönetilebilir hale gelecektir. Tüm gradleları hızlı biçimde version cataloga geçirip en son aşamada sync almak süreci epey zorlaştıracak ve bir çok farklı problemlere yol açacaktır.
Avantaj✔️ & Dezavantaj✖️
Okunabilirlik ve Yönetim Kolaylığı: Bağımlılıkların tek bir katalogda toplanması, gradle dosyalarının daha okunabilir ve yapılacak refactor işlemlerinin daha kolay hale gelmesini sağlar. Bu, ekip arkadaşlarının proje kodlarını ve bağımlıklıları daha iyi anlaması açısından faydalı olacaktır.
Tekrarlanan Kodun Azaltılması: Version Catalog, projede tekrarlanan bağımlılık tanımlarını azaltır. Aynı bağımlılığın farklı modüllerde tekrar tekrar tanımlanması yerine, tek bir katalogdan referans verilmesi yeterlidir. Örnek verecek olursak her gradle için java version 11–18 gibi hardcoded biçimde yazıyor olmak ve zamanı geldiğinde değiştirmek istiyorsak tüm dosyalarda refactor işlemi gerekecek. Catalog sayesinde tek yerden versionları yönetiyor oluyoruz.
Bundle kullanımı: Bence en büyük avantajı bundle yazılması o yüzden sonra sakladım diyebilirim.
Doğrudan bir örnek ile açıklayacak olursak UnitTest implementasyonu için minimum 7–5 tane libi her gradle a tek tek implemente etmek zorunda kalıyorduk yukarıda da örneğini vermiştim. Version catalog sayesinde ilgili toml dosyasında bu impl’ler için bir bundle yazıp 8 satırda yapılan işlemi tek satıra indirgemiş oluyoruz. İlerleyen süreçlerde bu testler ile ilgili yeni bir impl. Eklenecek ve ilgili gradlelarda kullanılması gerkecekse tek tek eklemek yerine ilgili bundle içerisine ekleyip tek satırdan işimizi çözüyor olacağız. Sadece Unittest olarak düşünmemek gerekir birbiri ile bağımlı olan ve aynı anda implemente edilmesi gereken tüm libraryler için bundlelar yazılıp kullanılabilir. (Test, Hilt, Coroutine vs.)
Toml dosyası içerisinde Test Libleri için yazılan bundle;
[bundles]
tests = [
"junit",
"junitktx",
"mockk",
"mockkAgent",
"testCoroutines",
"testCore",
"testCoreKtx",
"truth",
"turbine",
"archCoreTest"
]
Versiyon Catalog sonrası bundle ile unit test dependency’lerin eklenmesi;
implementation(libs.bundles.tests)
Version Catalog öncesi unit test dependency’lerin eklenmesi;
// Groovy ile Library Impl.
implementation("junit:junit:4.13.2")
implementation("com.google.truth:truth:1.1.2")
implementation("androidx.test.ext:junit-ktx:1.1.3")
implementation("androidx.arch.core:core-testing:2.1.0")
implementation("io.mockk:mockk:1.13.5")
implementation("io.mockk:mockk-agent-jvm:1.13.5")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.2")
implementation("androidx.test:core-ktx:1.4.0")
implementation("androidx.test:core-ktx:1.4.0")
implementation("app.cash.turbine:turbine:1.0.0")
Entegrasyon kısmında liblerin tek tek implemente edilmesine dair bir kod bloğu paylaşmıştım. Entegrasyon kısmındaki resimde de görüldüğü gibi 10 satırda yapılacak işlemi tek satırda bitirmiş oluyoruz. Bu nedenle büyük projeler için Version Catalog yönetim ve bakım açımızdan işimizi oldukça kolaylaştırıyor.
Version Catalog’a geçişte özellikle Groovy ile gradle yazmaya alışkın olanlar için ilk başta zor olarak görünebilir fakat biraz inceleme ve araştırmadan sonra Groovy’nin aslında daha zor olduğunu görmüş olacağız 😊.
Build Süresine olan etkisi ⌛️⏳
Version catalog için Dsl gibi build süresini arttırıyor ya da azaltıyor şeklinde doğrudan etkisi olduğunu söylemek doğru olmaz. Projeden projeye farklılık gösterecektir. Bu süreçte bağımlılıkları kontrol ederek gitmek önemlidir. Gereksiz lib implementasyonların ve bağımlılıklardan kaçınmamız gerekiyor. Bunlar doğrudan olmasa da dolaylı yoldan build süremizi uzatabilir. Catalogun yanlış configure edilmesi build süresini uzatabilir. Bunlardan farklı olarak build cache’i etkin kullanabilmeyi sağladığı ve bu sayede build süresini kısaltabildiği de söyleniyor.
Entegrasyon 📖
Groovy kullanırken config.gradle, Dsl geçişinden sonra ise Dependencies ve Versions adlı classlar ile yönetim sağlanıyordu. Cataloga geçişin ilk aşaması olan toml dosyası oluşturulduktan sonra tüm version ve library tanımlarını libs.versions.toml dosyasına taşımamız gerekiyor.
Örnekler üzerinden ilerleyecek olursak. Kullanılan liblerin versiyonlarını toml içerisinde versions dizini altında aşağıdaki resimde görüldüğü gibi tanımlamak gerekiyor.
[versions]
# test library versions
junit = "4.13.2"
truth = "1.1.2"
testCore = "1.4.0"
testCoreKtx = "1.4.0"
junitktx = "1.1.3"
mockk = "1.13.5"
mockkAgent = "1.13.5"
archCoreTest = "2.1.0"
turbine = "1.0.0"
Sonraki aşama olarak lib tanımlarını yapmamız gerekiyor yine test libleri için toml dosyasında libraries dizini altında resimde görüldüğü gibi eklemek gerekiyor.
[libraries]
# test libraries
junit = { group = "junit", name = "junit", version.ref = "junit" }
truth = { group = "com.google.truth", name = "truth", version.ref = "truth" }
junitktx = { group = "androidx.test.ext", name = "junit-ktx", version.ref = "junitktx" }
archCoreTest = { group = "androidx.arch.core", name = "core-testing", version.ref = "archCoreTest" }
mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk" }
mockkAgent = { group = "io.mockk", name = "mockk-agent-jvm", version.ref = "mockkAgent" }
testCoroutines = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "kotlinx_coroutines" }
testCoreKtx = { group = "androidx.test", name = "core-ktx", version.ref = "testCoreKtx" }
testCore = { group = "androidx.test", name = "core-ktx", version.ref = "testCore" }
turbine = { group = "app.cash.turbine", name = "turbine", version.ref = "turbine" }
Yukarıda belirtildiği gibi gradlelarda artık libs.junit vs diyerek ilgili lib implementasyonlarını sağlamış oluyoruz. Bu aşamada refactor işlemine tabi ki başlanabilir ama güzel bir özellik olan bundle yazımından avantajlar kısmında bahsetmiştim.
Aşağıdaki kod bloklarında Version Catalog öncesi Groovy ile kullanımı ve Version Catalog sonrası kullanımı görebilirsiniz
// Groovy ile Library Impl.
implementation("junit:junit:4.13.2")
implementation("com.google.truth:truth:1.1.2")
implementation("androidx.test.ext:junit-ktx:1.1.3")
implementation("androidx.arch.core:core-testing:2.1.0")
implementation("io.mockk:mockk:1.13.5")
implementation("io.mockk:mockk-agent-jvm:1.13.5")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.2")
implementation("androidx.test:core-ktx:1.4.0")
implementation("androidx.test:core-ktx:1.4.0")
implementation("app.cash.turbine:turbine:1.0.0")
// Version Catalog ile Library Impl.
implementation(libs.junit)
implementation(libs.truth)
implementation(libs.junitktx)
implementation(libs.archCoreTest)
implementation(libs.mockk)
implementation(libs.mockkAgent)
implementation(libs.testCoroutines)
implementation(libs.testCoreKtx)
implementation(libs.testCore)
implementation(libs.turbine)
Okuduğunuz için teşekkürler. Bir sonraki yazımız olan CI/CD Processes using Azure Devops in QNB Android Mobile’ da görüşmek üzere.