Jetpack Compose Side Effect’ler
Merhaba bu yazımızda herhangi bir Jetpack Compose uygulama geliştirmeden önce bilmemiz gereken Side effectlerden bahsedeceğiz.
Nedir bu side effectler?
Bildiğiniz üzere xml geliştirilen android uygulamalarda ekranların yaşam döngülerine göre bazı işlemler yapabiliyoruz. Bunlara onCreate
ya da onResume
methodlarını örnek verebiliriz. Jetpack compose ile geliştirilen uygulamalarda ise her bir composable methodu bir ekran gibi düşünebiliriz. Ancak direkt composable method içerisinde method create mi oluyor yoksa destroy mu oluyor dinleyemeyiz. Bunun en büyük sebebi composable methodlar bazı durumlarda peş peşe birçok kez çalışabiliyor. Eğer onCreate
için gerekli kodları direkt composable method içine yazarsak bu o kodun peş peşe tekrar çalşmasına sebep olacaktır. Bu side effectlerin faydalı olduğu alanlardan birisi. Ayrıca yaşam döngüsü değişikliklerinin yanında bir parametrenin değiştiği durumlarda da methodun çalışmasını istiyorsak yine side effectlerin kullanılması gerekmektedir. Hadi şu side effectlere daha yakından bakalım:
LaunchedEffect
LaunchedEffect
içine verilen parametre her güncellendiğinde tetiklenen bir methoddur. Eğer içine verdiğiniz parametre hiç güncellenmeyecekse bile Composable method ilk kez çalıştığında bir kere çalışacaktır.
@Composable
@NonRestartableComposable
@OptIn(InternalComposeApi::class)
fun LaunchedEffect(
key1: Any?,
block: suspend CoroutineScope.() -> Unit
) {
val applyContext = currentComposer.applyCoroutineContext
remember(key1) { LaunchedEffectImpl(applyContext, block) }
}
Yukarıda LaunchedEffect
methodunun aldığı parametreleri görüyorsunuz. key parametresi sadece bir tane görünse de sınırsızdır. Birden fazla parametreye aynı anda bakabilir. block parametresi ise görüldüğü üzere coroutine scope içinde bir callback methodtur. Bu da demektir ki Side effectler içerisindeki işlemler asenkron olarak çalışırlar.
Dipnot: Yukarıda da dediğimiz gibi side effectler coroutine method içerisinde çalıştığı için ilk işlem bitmeden izlenen parametre değişirse bir önceki scope işlemi iptal edilir ve yeni işlem başlatılır.
DisposableEffect
Bu side effect diğerleri LaunchedEffect
gibi bir parametrenin değişmesi durumunda ya da composable methodun yaşam döngüsü değişmesi durumlarında çalışır. LaunchedEffect
den farklı olarak kendi içerisinde onDispose
callback methodu bulunur. Bu method composable methodumuz ekrandan kaldırıldığı anda tetiklenir. Bu sayede eğer ekran mevcut ekran kapandığında yapmak istediğimiz bir işlem var ise (Örn: veri tabanı bağlantısını kapatmak) bunun içinde yapabiliriz.
val dbConnection = remember { createDatabaseConnection() }
DisposableEffect(Unit) {
dbConnection.connect()
onDispose {
dbConnection.close()
}
}
Yukarıdaki DisposableEffect
kodunu inceleyecek olursak; key parametresi olarak Unit değeri verilmiş, bu da demek oluyor ki parametre hiçbir zaman değişmeyecek. İçerisinde bulunan kodumuz da sadece composable methodumuz ilk kez oluştuğunda çalışacak ve composable methodumuz ekrandan kaldırılırken de onDispose
içindeki kodumuz çalışacak.
Bu sayede veri kaynadığının daha düzgün yönetilmesini sağlayabiliriz. Örneğin açık olan bir veritabanı bağlantısını kapatmak ya da hali hazırda kullanılan bir telefon özeliğinin kapatılması gibi işleri bu alan içerisinde yapabiliriz.
SideEffect
Bu side effectimiz ise composable methodumuz içerisindeki bir değer her güncellendiğinde tekrar çalışır. Bu sayede herhangi bir yeni değer durumuna göre yapmak istediğimiz bir işlem var ise bunu yapabiliriz.
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) }
// Doğrudan Composable içine kod yazma
count++
Text("Count: $count")
}
Bu örnekte, Counter
adlı Composable işlevi, her çağırıldığında count
değerini artırır ve günceller. Bu, her çağırıldığında count
değerinin artırılmasını sağlar.
Örnek: SideEffect
İşlevi Kullanmak
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) }
SideEffect {
// count değeri her değiştiğinde çalışacak kodlar
count++
println("Count değeri güncellendi: $count")
}
Text("Count: $count")
}
Bu örnekte, SideEffect
işlevi kullanıyoruz ve count
değeri her değiştiğinde çalışacak kodları içeriyor. Yani her count
değeri değiştiğinde, bu kodlar çalışır ve count
değeri güncellenir. SideEffect
içindeki kodlar, her count
değişikliğinde çalıştığı için her güncelleme sonrası count
değeri 1 artırılır.
Farkı anlamak için her iki örneği karşılaştırabiliriz:
- İlk örnekte,
count
değeri her çağırıldığında artırılır ve güncellenir, ancak bu sadeceCounter
işlevi içinde çalışır. - İkinci örnekte ise,
SideEffect
içindeki kodlar, hercount
değişikliğinde çalışır ve bu nedenle her güncelleme sonrasıcount
değeri artırılır.
Yani, SideEffect
işlevini kullanarak count
değeri her güncellendiğinde çalışacak kodları belirlemiş oluyoruz. Eğer bu kodları doğrudan Composable içine yazsaydık, her çağırıldığında çalışır ve count
değeri her seferinde artmazdı. Bu nedenle SideEffect
, belirli bir veri değişikliği veya koşula bağlı olarak işlem yapmak istediğinizde kullanışlıdır.
rememberCoroutineScope
Composable methodlar içerisinde nasıl asenkron ya da methodun güncellenmesine göre işlemler yapabileceğimizden bahsettik. Peki ya bir alt composable methoddan gelen gelen eventlere göre işlem yapmamız gerekirse? Ya da daha açık şekilde bir button composablenin click eventi bulunmakta ve kullanıcı butona tıkladığında api isteği atmamız gerekirse ne yapmalıyız?
Biliyoruz ki api isteği gibi işlerin hepsini asenkron şekilde coroutine scopelar içerisinde yapmamız gerekir. Bunun ana sebebi genel uygulama akışını kilitlememektir. LaunchedEffect
ya da DisposableEffect
gibi side effectleri dolaylı yoldan tetikleyerek coroutine scope içerisinde istek atmamız mümkün. Ancak jetpack compose ayrıca rememberCoroutineScope
methodu sayesinde composable methodlar içerisinde ayrı bir coroutine scope oluşturmamıza olanak tanıyor.
@Composable
fun MoviesScreen(snackbarHostState: SnackbarHostState) {
val scope = rememberCoroutineScope()
Scaffold(
snackbarHost = {
SnackbarHost(hostState = snackbarHostState)
}
) { contentPadding ->
Column(Modifier.padding(contentPadding)) {
Button(
onClick = {
// Create a new coroutine in the event handler to show a snackbar
scope.launch {
snackbarHostState.showSnackbar("Something happened!")
}
}
) {
Text("Press me")
}
}
}
}
Yukarıdaki örnekte görüldüğü gibi butona tıklandığında snackbar gösterilme işi coroutineScope
içerisinde yapılıyor. Bunun sebebi snackbarın barındırdığı animasyonlardır. Asenkron çalıştırarak ana threadi kitlemeden animasyonu çalıştırmış oluyoruz.
rememberUpdatedState
Bu side effectimiz ise kendisine verilen değişkenin güncellenme durumunu kontrol eder. Eğer verilen değişkenin güncellenmez ise kendisini güncellemez ancak verilen değişken güncellenirse kendisi de bu güncel değeri tutar ve kendisini dinleyen viewlerin de güncellenmesini sağlar.
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) }
val updatedCount by rememberUpdatedState(count)
Button(onClick = { count++ }) {
Text("Artır")
}
Text("Count: $updatedCount")
val isEven = updatedCount % 2 == 0
val message = if (isEven) "Çift sayıdır." else "Tek sayıdır."
Text(message)
}
yukarıdaki örnekte göreceğimiz üzere count değeri çift ya da tek olarak güncellendiğinde updatedCount
değerine bakıyor ve texti çiziyoruz. Ancak eğer count değerine her seferinde aynı değeri atıyor olsaydık ekran sadece bir kez güncelleniyor olacaktı.
Bu yazımızda da side effectlerin projemizdeki kullanımlarından ve ne işe yaradıklarından bahsettik. Umarım faydalı olmuştur ve aklınızdaki soruları gidermeye yaramıştır. Bir sonraki yazıda görüşmek üzere Ciao!