Using CI/CD with Remote Config in Android applications
Mobil uygulama geliştirmede yapılan bir hatanın maliyeti geleneksel yazılım geliştirmeye göre daha yüksektir. Bunun sebebi deploy(dağıtımın) yavaş olması,mağaza onay süreçleri ve son kullanıcıya ulaşsa bile kullanıcının son sürüme geçeceğinin garantisinin olmamasıdır.
Android uygulama geliştirme özelinde bu süreç içerisine binlerce cihaz ve farklı API lvl’leri de katılmaktadır. Bu durum android/mobil uygulama geliştirme için hızlı karar alıp uygulamayı zorlaştırmaktadır.
Bu aşamada yardımımıza CI/CD tool’ları yetişiyor. Basitce CI ve CD’nin ne olduğuna bakalım.
CI(Continuous Integration) : Geliştirme süreçlerinde farklı özellikler ve işlevler için farklı geliştiriciler görevlendirilir. Her geliştirici kendi branch’inde görevini tamamlar ve bir noktada PR açar. Geliştiricinin kodu diğer geliştiriciler tarafından onaylanmadan kontrol/test edilmesi gereklidir belirli bir standart veya belirlenen kararların kontrol edilmesi gerekir. Tüm süreç geliştiriciler için tekrar tekrar kontrol edilir. CI’ın yararlı olduğu nokta burasıdır. PR açıldığında bir CI süreci tetiklenir ve süreç otomatik olarak başlatılır. Kısaca kod alınır, derlenir, istenilen testler çalıştırılır ve sonuçları kontrol edilir.
CD(Continuous Delivery) : CI süreci tamamlanıp kod main/master branch de birleştirildikten sonra kodun production ortamına geçişine kadar yapılan adımların otomatikleştirilmesini sağlar.Bu aşamada kod QA takımları tarafından hazırlanan testler veya kritik öneme sahip testleri yapılır. APK,AAB olarak istenilen format belirlenir ve KeyStore oluşturularak şifrelenir. Süreçler tamamlandıktan sonra istenilen bir anda veya tamamlandıktan sonra deploy edilir.
Genel olarak kavramlara aşina olduğumuza göre bunların örneklerine bakalım. Örneklerin tamamı github reposu üzerinde olucak.
CI süreçleri için github actions’u kullandım. Github actions ile istediğimiz kritelerde yml dosyaları oluşturarak tetikleyiciler(pull request,push…) kullanıyoruz. Belirlediğimiz tetikleyiciler çalıştığında yml dosyalarımızda belirttiğimiz step’ler çalışmaya başlıyor unit test,emulator vs. Burada her çalışmada sıfırdan bir cihaz üzerinde çalıştığı için local’de farklı prod’da farklı durumların ortaya çıkması engelleniyor.Repoda şu anda unit testlerin çalıştırıldığı ve commit mesajları içerisinde belirli bir kelimeyi kontrol eden yml dosyaları bulunmakta. Aynı zamanda isteklerimize göre testler,workflow’lar çalıştırabiliyoruz. Detaylı bilgi için github actions belgelerine bakabilirsiniz.
Firebase Remote Config ile uygulamalarına bakalım.
Remote Config ile double,long,string,boolean ve json olarak parametreleri iletebiliyoruz. Burada key’lere dikkat etmemiz gerekiyor her bir key’in karşılığının client tarafında varsayılan bir değerinin olması gerekli. Remote Config tarafından gelen parametreleri (presentation)res-xml paketi altında bir xml dosyasında tutuyoruz. Burada değerler gelmediğinde kullanılacak varsayılan değerler bulunuyor.
Burada yer alan her bir değerin karşılığı Firebase Remote Config’de tutulmakta
Remote Config tarafında Condition’ları kullanarak aynı key için farklı değerler verebiliyoruz. Burada yapabileceklerimiz rastgele kullanıcılar belirlemek,konum,kullanılan dil,tarih-saat dilimine,platform(Android-IOS) vs. gibi parametreleri verebiliyoruz.
Proje içerisinde örnek olarak
- Bakım/servis durumlarında bir mesaj gösterilmesi,
- A/B testleri için kullanıcıların aynı işlevde ama farklı ekranların gösterilmesi. Tek ekranlı veya çok ekranlı kullanıcı kaydı gibi
- Yeni özelliklerin tanıtılması için alert dialog gösterilmesi
- Lokasyon bazlı özel banner’lar gösterilmesi
- Slider içerisinde dinamik liste gösterilmesi
- Kullanıcıların fikirlerini/görüşlerini toplamak için istenilen zamanlarda bottom sheet ile veriler toplamak
Çeşitli kullanım örnekleri yer almakta. Slider örneği üzerinden kodlara bakalım. Uygulamalar içerisinde dinamik olarak değişen haberler,linkler olmakta. Dinamik olarak bu görsel ve linkler değiştirilmekte.Örnek üzerinde dinamik olarak istediğimiz an görseli,başlığı ve link’i değiştirebiliyoruz.
Remote Config tarafında json içerisinde listemiz var.
Client tarafında json değerlerinin varsayılan değeri, RecyclerView ve linkler için Intent yapısı bulunmakta. Remote Config’i kullanmak için client tarafında nesnesini(Hilt kullanılmakta)oluşturalım.
fun provideRemoteConfig(): FirebaseRemoteConfig {
return FirebaseRemoteConfig.getInstance().apply {
val remoteConfigSettings = FirebaseRemoteConfigSettings.Builder().setMinimumFetchIntervalInSeconds().build()
setConfigSettingsAsync(remoteConfigSettings)
setDefaultsAsync(R.xml.network_security_config)
}
}
setMinimumFetchIntervalInSeconds -> Değişiklikleri ne kadar sürede almak istediğimizi ayarlıyoruz.
setConfigSettingsAsync -> FirebaseRemoteConfigSettings nesnesini ayarlamamızı sağlıyor
setDefaultAsync -> Varsayılan olarak yapılandırılacak değerleri tutan xml dosyasını ayarlıyoruz
FirebaseRemoteConfig nesnesini oluşturduktan sonra değerleri nesne üzerinden değerleri almamız gerekiyor. Benzer işlevler bir çok yerde kullanıldığı için bunları bir extension üzerinde birleştirdim.
inline fun <reified T> FirebaseRemoteConfig.fetchToLiveData(
key: String,
gson: Gson,
mutableLiveData: MutableLiveData<T>
) {
this.fetchAndActivate().addOnCompleteListener {
if (it.isSuccessful) {
val keyString = this.getString(key)
val type = object : TypeToken<T>() {}.type
val jsonModel = gson.fromJson<T>(keyString, type)
mutableLiveData.postValue(jsonModel)
}
}
}
fetchAndActivate -> Firebase Remote Config üzerinden sunucusundan verileri getiriyor.
addOnCompleteListener -> işlemin tamamlanmasını takip eder
Listener’ın başarılı şekilde tamamlanıp tamamlanmadığını kontrol etmek için it.isSuccessful’u kullanıyoruz. Başarılı şekilde veriler gelmişse yapılacak işlemleri belirtiyoruz. Ayarladığımız değerlere göre getString,getDouble,getBoolean ve getLong kullanıyoruz. Json değerleri için de getString’i kullanıyoruz. Json değeri kullanabilmek için Gson’u kütüphanesini kullanarak verileri bir data class’a convert ediyoruz.
data class SliderModel(
@SerializedName("slider_image_url")
val sliderImage : String,
@SerializedName("slider_intent_url")
val intentUrl : String,
@SerializedName("slider_text")
val sliderText : String
)
Son olarak değerleri kullanabilmek için MutableLiveData nesnesine değeri aktarıyoruz.
Kısaca yaptıklarımız aynı key’leri olan Firebase Remote Config sunucusunda ki değerlerin karşılığı olarak Client’de yapılara hazırlamak. Böylelikle kontrolümüz dışında olan veya hızlı karar almamız gereken durumlarda Client’e müdahale ederek herhangi bir sürüm çıkmadan kullanıcılar üzerinde farklı workflow’lar çalıştırabiliyoruz.
Slider için extension fonksiyonu kullandığımız HomeViewModel ->
private val _homeSlider = MutableLiveData<List<SliderModel>>()
val homeSlider : LiveData<List<SliderModel>> = _homeSlider
fun homeSliderRemoteConfig(){
remoteConfig.fetchToLiveData("home_slider",gson,_homeSlider)
}
LiveData’yı HomeFragment içinde observe ederek adapter için listemizi ve her bir item’a tıklanıldığında gerekli link’e gidebilmek için intent’i içeren fonksiyonumuzu click olarak veriyoruz
private fun observe() {
observeIfNotNull(viewModel.homeSlider,::setSlider)
}
private fun setSlider(listSlider : List<SliderModel>){
sliderAdapter.updateRecyclerList(listSlider)
}
private fun listener(){
binding.apply {
sliderAdapter.sliderClickListener(::openWithIntent)
}
}
private fun openWithIntent(intentUrl: String){
val urlIntent = Intent(
Intent.ACTION_VIEW,
Uri.parse(intentUrl)
)
startActivity(urlIntent)
}
Böylelikle dinamik olarak slider’ı kontrol edebiliyoruz.
Kısaca CI/CD tool’larını kullanarak manuel olarak yaptığımız kontrol ve tekrarlı işlemleri otomatik olarak yapacak yapılandırıcıları kullanarak Geliştirme(development) ve Dağıtma(deploy) süreçlerini kolaylaştırıyoruz.Remote Config gibi sistemlerle client tarafındaki kodları yapılandırarak kullanıcılar için dinamik olarak güncelleme çıkmadan farklı kullanıcı akışları/workflow’lar uygulayabiliyoruz. Best practies olarak CI/CD toolları bizlere Zaman kazandırmakta,süreçlerin takip edilmesini kolaylaştırmakta,manuel işlemleri otomatikleştirmekte, sıfırdan cihazlar üzerinde çalıştığı için local de farklı, prod da farklı çalışmamakta izolasyon sağlamakta. Son olarak sertifika ve Keystore gibi önemli bilgi/belgelerin bir yerde korunmasını sağlamakta.
Daha fazla örnek için repoyu inceleyebilirsiniz. Görüş öneri veya eleştirileriniz var ise linkedin veya twitter’dan ulaşarak belirtirseniz sevinirim.
Kaynaklar :