Yan etki, uygulamanın durumunda, bir derlenebilir işlevin kapsamı dışında gerçekleşen bir değişikliktir. Birleştirilebilir öğelerin yaşam döngüsü ve öngörülemeyen yeniden oluşturma, farklı sıralarda yeniden oluşturma veya reddedilebilir yeniden oluşturma gibi özellikleri nedeniyle, birleştirilebilir öğeler ideal olarak yan etki içermemelidir.
Ancak bazen yan etkiler gereklidir. Örneğin, belirli bir durum koşulu verildiğinde bir bilgi çubuğu gösterme veya başka bir ekrana gitme gibi tek seferlik bir etkinliği tetiklemek için yan etkiler gereklidir. Bu işlemler, derlenebilir öğenin yaşam döngüsünü bilen kontrollü bir ortamdan çağrılmalıdır. Bu sayfada, Jetpack Compose'un sunduğu farklı yan etki API'leri hakkında bilgi edineceksiniz.
Durum ve etki kullanım alanları
Thinking in Compose dokümanında belirtildiği gibi, derlenebilirler yan etki içermemelidir. Uygulamanın durumunda değişiklik yapmanız gerektiğinde (Durum yönetimi dokümanlarında açıklandığı gibi), bu yan etkilerin tahmin edilebilir bir şekilde yürütülmesi için Etki API'lerini kullanmanız gerekir.
Oluşturma'da efektler sayesinde sunulan farklı olanaklar nedeniyle efektler kolayca aşırı kullanılabilir. Bu dosyalarda yaptığınız çalışmaların kullanıcı arayüzüyle ilgili olduğundan ve Durum yönetimi dokümanlarında açıklandığı gibi tek yönlü veri akışını bozmadığından emin olun.
LaunchedEffect
: Askıya alma işlevlerini bir composable kapsamında çalıştırma
Bir composable'ın ömrü boyunca işlem yapmak ve askıya alma işlevlerini çağırma olanağına sahip olmak için LaunchedEffect
composable'ı kullanın. LaunchedEffect
, kompozisyona girdiğinde parametre olarak iletilen kod bloğunu içeren bir coroutine başlatır. LaunchedEffect
kompozisyondan ayrılırsa coroutine iptal edilir. LaunchedEffect
farklı anahtarlarla yeniden derlenirse (aşağıdaki Efektleri Yeniden Başlatma bölümüne bakın) mevcut iş parçacığı iptal edilir ve yeni askıya alma işlevi yeni bir iş parçacığında başlatılır.
Örneğin, alfa değerini yapılandırılabilir bir gecikmeyle yanıp sönen bir animasyon:
// Allow the pulse rate to be configured, so it can be sped up if the user is running // out of time var pulseRateMs by remember { mutableStateOf(3000L) } val alpha = remember { Animatable(1f) } LaunchedEffect(pulseRateMs) { // Restart the effect when the pulse rate changes while (isActive) { delay(pulseRateMs) // Pulse the alpha every pulseRateMs to alert the user alpha.animateTo(0f) alpha.animateTo(1f) } }
Yukarıdaki kodda animasyon, belirlenen süreyi beklemek için duraklatma işlevini delay
kullanır. Ardından, animateTo
kullanarak alfa değerini sıfıra ve tekrar sıfıra kadar sırayla animasyonlu olarak değiştirir.
Bu işlem, bileşenin kullanım ömrü boyunca tekrarlanır.
rememberCoroutineScope
: Bir bileşiğin dışında bir coroutine başlatmak için bileşime duyarlı bir kapsam elde edin
LaunchedEffect
, composable bir işlev olduğundan yalnızca diğer composable işlevlerin içinde kullanılabilir. Bir composable'ın dışında eş yordam başlatmak için, ancak kapsamı, besteden ayrıldıktan sonra otomatik olarak iptal edilecek şekilde ayarlamak için rememberCoroutineScope
kullanın.
Ayrıca, bir veya daha fazla coroutine'ün yaşam döngüsünü manuel olarak kontrol etmeniz gerektiğinde (ör. bir kullanıcı etkinliği gerçekleştiğinde bir animasyonu iptal etmek) rememberCoroutineScope
değerini kullanın.
rememberCoroutineScope
, çağrıldığı kompozisyon noktasına bağlı bir CoroutineScope
döndüren birleştirilebilir bir işlevdir. Görüşme, kompozisyondan çıktığında kapsam iptal edilir.
Önceki örneğe göre, kullanıcı bir Button
düğmesine dokunduğunda Snackbar
görüntülemek için şu kodu kullanabilirsiniz:
@Composable fun MoviesScreen(snackbarHostState: SnackbarHostState) { // Creates a CoroutineScope bound to the MoviesScreen's lifecycle 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") } } } }
rememberUpdatedState
: Değeri değişirse yeniden başlatılmaması gereken bir efektteki bir değere referans verme
LaunchedEffect
, temel parametrelerden biri değiştiğinde yeniden başlatılır. Ancak bazı durumlarda, efektinizde bir değeri yakalamak isteyebilirsiniz. Değişmesi durumunda efektin yeniden başlatılmasını istemezsiniz. Bunu yapmak için rememberUpdatedState
kullanarak bu değere kaydedilip güncellenebilecek bir referans oluşturmalısınız. Bu yaklaşım, yeniden oluşturma ve yeniden başlatmanın pahalı veya yasaklayıcı olabileceği uzun süreli işlemler içeren efektler için yararlıdır.
Örneğin, uygulamanızda bir süre sonra kaybolan bir LandingScreen
olduğunu varsayalım. LandingScreen
yeniden derlense bile, bir süre bekleyip sürenin geçtiğini bildiren efektin yeniden başlatılmaması gerekir:
@Composable fun LandingScreen(onTimeout: () -> Unit) { // This will always refer to the latest onTimeout function that // LandingScreen was recomposed with val currentOnTimeout by rememberUpdatedState(onTimeout) // Create an effect that matches the lifecycle of LandingScreen. // If LandingScreen recomposes, the delay shouldn't start again. LaunchedEffect(true) { delay(SplashWaitTimeMillis) currentOnTimeout() } /* Landing screen content */ }
Arama sitesinin yaşam döngüsüyle eşleşen bir efekt oluşturmak için parametre olarak Unit
veya true
gibi hiç değişmeyen bir sabit iletilir. Yukarıdaki kodda LaunchedEffect(true)
kullanılır. onTimeout
lambda'sının her zaman LandingScreen
ile yeniden derlenen en son değeri içerdiğinden emin olmak için onTimeout
'ün rememberUpdatedState
işleviyle sarmalanması gerekir.
Kodda döndürülen State
, currentOnTimeout
efekti kullanmalıdır.
DisposableEffect
: temizlenmesi gereken efektler
Anahtarlar değiştikten sonra temizlik yapılması gereken yan etkiler için veya kompozisyondan ayrılan kompozisyonlar için DisposableEffect
kullanın.
DisposableEffect
anahtarları değişirse derlenebilir öğenin mevcut efektini yok etmesi (temizlemesi) ve efekti tekrar çağırarak sıfırlaması gerekir.
Örneğin, LifecycleObserver
kullanarak Lifecycle
etkinliklerine dayalı analiz etkinlikleri göndermek isteyebilirsiniz.
Compose'da bu etkinlikleri dinlemek için DisposableEffect
kullanarak gözlemciyi kaydedin ve gerektiğinde gözlemcinin kaydını iptal edin.
@Composable fun HomeScreen( lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current, onStart: () -> Unit, // Send the 'started' analytics event onStop: () -> Unit // Send the 'stopped' analytics event ) { // Safely update the current lambdas when a new one is provided val currentOnStart by rememberUpdatedState(onStart) val currentOnStop by rememberUpdatedState(onStop) // If `lifecycleOwner` changes, dispose and reset the effect DisposableEffect(lifecycleOwner) { // Create an observer that triggers our remembered callbacks // for sending analytics events val observer = LifecycleEventObserver { _, event -> if (event == Lifecycle.Event.ON_START) { currentOnStart() } else if (event == Lifecycle.Event.ON_STOP) { currentOnStop() } } // Add the observer to the lifecycle lifecycleOwner.lifecycle.addObserver(observer) // When the effect leaves the Composition, remove the observer onDispose { lifecycleOwner.lifecycle.removeObserver(observer) } } /* Home screen content */ }
Yukarıdaki kodda efekt, observer
öğesini lifecycleOwner
öğesine ekler. lifecycleOwner
değişirse efekt atılır ve yeni lifecycleOwner
ile yeniden başlatılır.
DisposableEffect
, kod bloğunun son beyanı olarak bir onDispose
yan tümcesi içermelidir. Aksi takdirde IDE, derleme zamanı hatası gösterir.
SideEffect
: Oluştur durumunu, Compose olmayan koda yayınla
Oluşturma durumu, oluşturma tarafından yönetilmeyen nesnelerle paylaşmak için SideEffect
composable öğesini kullanın. SideEffect
kullanmak, efektin her başarılı yeniden oluşturma işleminden sonra yürütülmesini sağlar. Diğer yandan, başarılı bir yeniden düzenleme garanti edilmeden önce bir efektin gerçekleştirilmesi yanlıştır. Bu, efektin doğrudan composable'a yazılması durumunda geçerlidir.
Örneğin, analiz kitaplığınız, sonraki tüm analiz etkinliklerine özel meta veriler ("kullanıcı özellikleri" bu örnekte) ekleyerek kullanıcı kitlenizi segmentlere ayırmanıza olanak tanıyabilir. Mevcut kullanıcının kullanıcı türünü analiz kitaplığınıza iletmek için değerini güncellemek üzere SideEffect
kullanın.
@Composable fun rememberFirebaseAnalytics(user: User): FirebaseAnalytics { val analytics: FirebaseAnalytics = remember { FirebaseAnalytics() } // On every successful composition, update FirebaseAnalytics with // the userType from the current User, ensuring that future analytics // events have this metadata attached SideEffect { analytics.setUserProperty("userType", user.userType) } return analytics }
produceState
: Oluşturma dışındaki durumu Oluşturma durumuna dönüştürme
produceState
, değerleri döndürülen State
öğesine aktarabilen Besteye ayarlanan bir eş yordam başlatır. Oluşturma dışındaki durumu Oluşturma durumuna dönüştürmek için kullanın. Örneğin, Flow
, LiveData
veya RxJava
gibi harici abonelik odaklı durumları Oluşturma'ya dahil edin.
Üretici, produceState
kompozisyona girdiğinde başlatılır ve kompozisyondan çıktığında iptal edilir. Döndürülen State
değeri birleştirilir; aynı değerin ayarlanması yeniden oluşturmayı tetiklemez.
produceState
bir coroutine oluştursa da askıya alınmayan veri kaynaklarını gözlemlemek için de kullanılabilir. Söz konusu kaynağa aboneliği kaldırmak için awaitDispose
işlevini kullanın.
Aşağıdaki örnekte, ağdan resim yüklemek için produceState
özelliğinin nasıl kullanılacağı gösterilmektedir. loadNetworkImage
birleştirilebilir işlevi, diğer birleştirilebilirlerde kullanılabilecek bir State
döndürür.
@Composable fun loadNetworkImage( url: String, imageRepository: ImageRepository = ImageRepository() ): State<Result<Image>> { // Creates a State<T> with Result.Loading as initial value // If either `url` or `imageRepository` changes, the running producer // will cancel and will be re-launched with the new inputs. return produceState<Result<Image>>(initialValue = Result.Loading, url, imageRepository) { // In a coroutine, can make suspend calls val image = imageRepository.load(url) // Update State with either an Error or Success result. // This will trigger a recomposition where this State is read value = if (image == null) { Result.Error } else { Result.Success(image) } } }
derivedStateOf
: Bir veya daha fazla durum nesnesini başka bir duruma dönüştürme
Oluşturma işleminde, gözlemlenen durum nesnesi veya birleştirilebilir giriş her değiştiğinde yeniden oluşturma gerçekleşir. Bir durum nesnesi veya giriş, kullanıcı arayüzünün güncellenmesi gerekenden daha sık değişiyor olabilir. Bu da gereksiz yeniden oluşturmaya neden olur.
Bir bileşiğe yaptığınız girişler, yeniden derlemeniz gerekenden daha sık değişiyorsa derivedStateOf
işlevini kullanmalısınız. Bu durum genellikle kaydırma konumu gibi sık sık değişen bir şey olduğunda ortaya çıkar. Ancak, bileşiğin yalnızca belirli bir eşiği aştığında buna tepki vermesi gerekir. derivedStateOf
, yalnızca ihtiyacınız olduğu kadar güncellenen yeni bir Oluşturma durumu nesnesi oluşturur. Bu şekilde, Kotlin Flows distinctUntilChanged()
operatörüne benzer şekilde çalışır.
Doğru kullanım
Aşağıdaki snippet, derivedStateOf
için uygun bir kullanım alanını göstermektedir:
@Composable // When the messages parameter changes, the MessageList // composable recomposes. derivedStateOf does not // affect this recomposition. fun MessageList(messages: List<Message>) { Box { val listState = rememberLazyListState() LazyColumn(state = listState) { // ... } // Show the button if the first visible item is past // the first item. We use a remembered derived state to // minimize unnecessary compositions val showButton by remember { derivedStateOf { listState.firstVisibleItemIndex > 0 } } AnimatedVisibility(visible = showButton) { ScrollToTopButton() } } }
Bu snippet'te, ilk görünür öğe her değiştiğinde firstVisibleItemIndex
değişir. Kaydırırken değer 0
, 1
, 2
, 3
, 4
, 5
vb. olur. Ancak yeniden derlemenin yalnızca değer 0
'ten büyükse yapılması gerekir.
Güncelleme sıklığındaki bu uyumsuzluk, bunun derivedStateOf
için iyi bir kullanım alanı olduğu anlamına gelir.
Yanlış kullanım
Sık yapılan bir hata, iki Compose durum nesnesini birleştirirken "durum türettiğiniz" için derivedStateOf
kullanmanız gerektiğini varsaymaktır. Ancak bu, aşağıdaki snippet'te gösterildiği gibi tamamen ek yüktür ve gerekli değildir:
// DO NOT USE. Incorrect usage of derivedStateOf. var firstName by remember { mutableStateOf("") } var lastName by remember { mutableStateOf("") } val fullNameBad by remember { derivedStateOf { "$firstName $lastName" } } // This is bad!!! val fullNameCorrect = "$firstName $lastName" // This is correct
Bu snippet'te fullName
, firstName
ve lastName
ile aynı sıklıkta güncellenmelidir. Bu nedenle, fazladan yeniden derleme gerçekleşmez ve derivedStateOf
kullanılması gerekmez.
snapshotFlow
: Compose'ın durumunu akışlara dönüştürme
State<T>
nesnelerini soğuk bir akışa dönüştürmek için snapshotFlow
simgesini kullanın. snapshotFlow
, toplandığında bloğunu çalıştırır ve içinde okunan State
nesnelerinin sonucunu yayınlar. snapshotFlow
bloğunda okunan State
nesnelerinden biri değiştiğinde, yeni değer önceki yayınlanan değere eşit değilse akış, yeni değeri toplayıcısına gönderir (bu davranış Flow.distinctUntilChanged
'e benzer).
Aşağıdaki örnekte, kullanıcı bir listedeki ilk öğenin ötesine kaydırdığında Analytics'e kaydedilen bir yan etki gösterilmektedir:
val listState = rememberLazyListState()
LazyColumn(state = listState) {
// ...
}
LaunchedEffect(listState) {
snapshotFlow { listState.firstVisibleItemIndex }
.map { index -> index > 0 }
.distinctUntilChanged()
.filter { it == true }
.collect {
MyAnalyticsService.sendScrolledPastFirstItemEvent()
}
}
Yukarıdaki kodda listState.firstVisibleItemIndex
, Akış operatörlerinin gücünden yararlanabilecek bir Akışa dönüştürülür.
Efektleri yeniden başlatma
Compose'daki LaunchedEffect
, produceState
veya DisposableEffect
gibi bazı efektler, çalışan efektini iptal etmek ve yeni anahtarlarla yeni bir tane başlatmak için kullanılan çeşitli sayıda bağımsız değişken ve anahtar alır.
Bu API'lerin tipik biçimi şu şekildedir:
EffectName(restartIfThisKeyChanges, orThisKey, orThisKey, ...) { block }
Bu davranışın incelikleri nedeniyle, efekti yeniden başlatmak için kullanılan parametreler doğru değilse sorunlar oluşabilir:
- Efektleri olması gerekenden daha az yeniden başlatmak uygulamanızda hatalara neden olabilir.
- Etkileri gereğinden fazla yeniden başlatmak verimsiz olabilir.
Genel kural olarak, koddaki efekt bloğunda kullanılan değişken ve değişmez değişkenler, efekt bileşimine parametre olarak eklenmelidir. Bunların dışında, efekti yeniden başlatmaya zorlamak için daha fazla parametre eklenebilir. Bir değişkenin değiştirilmesi efektin yeniden başlatılmasına neden olmayacaksa değişken rememberUpdatedState
içine alınmalıdır. Değişken, anahtar içermeyen bir remember
içine sarıldığı için hiçbir zaman değişmiyorsa değişkeni efektin anahtarı olarak iletmeniz gerekmez.
Yukarıda gösterilen DisposableEffect
kodunda bu efekt, lifecycleOwner
bloğunda kullanılan bir parametre olarak üstlenir. Çünkü bu kurallarda yapılan herhangi bir değişiklik, etkinin yeniden başlatılmasına neden olur.
@Composable
fun HomeScreen(
lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
onStart: () -> Unit, // Send the 'started' analytics event
onStop: () -> Unit // Send the 'stopped' analytics event
) {
// These values never change in Composition
val currentOnStart by rememberUpdatedState(onStart)
val currentOnStop by rememberUpdatedState(onStop)
DisposableEffect(lifecycleOwner) {
val observer = LifecycleEventObserver { _, event ->
/* ... */
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose {
lifecycleOwner.lifecycle.removeObserver(observer)
}
}
}
rememberUpdatedState
kullanımı nedeniyle, currentOnStart
ve currentOnStop
değerleri Kompozisyon'da hiçbir zaman değişmediğinden DisposableEffect
anahtarı olarak bunlara gerek yoktur. lifecycleOwner
parametresi iletilmezse ve değişirse HomeScreen
yeniden oluşturulur ancak DisposableEffect
kullanımdan kaldırılmaz ve yeniden başlatılmaz. Bu, o noktadan itibaren yanlış lifecycleOwner
kullanıldığı için sorunlara neden olur.
Anahtar olarak sabitler
Çağrı sitesinin yaşam döngüsünü takip etmesini sağlamak için efekt anahtarı olarak true
gibi bir sabit kullanabilirsiniz. Yukarıda gösterilen LaunchedEffect
örneği gibi geçerli kullanım alanları vardır. Ancak bunu yapmadan önce bir kez daha düşünün ve gerçekten ihtiyacınız olduğundan emin olun.
Sizin için önerilenler
- Not: JavaScript kapalıyken bağlantı metni gösterilir
- State ve Jetpack Compose
- Jetpack Compose için Kotlin
- Compose'da Görünümleri kullanma