Yan etki, uygulamanın durumunda composable işlevin kapsamı dışında gerçekleşen bir değişikliktir. composable'ların yaşam döngüsü ve öngörülemeyen yeniden kompozisyonlar gibi özellikleri nedeniyle, composable'ların farklı sıralarda yeniden kompozisyonları yürütme veya silinebilen yeniden kompozisyonlar gibi özellikleri nedeniyle, composable'lar ideal olarak yan etki içermemelidir.
Bununla birlikte, bazen atıştırmalık çubuğu gösterme veya belirli bir durum koşulunda başka bir ekrana gitme gibi tek seferlik bir etkinliği tetiklemek için yan etkiler gerekir. Bu işlemler, composable'ın 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ını belirtin
Thinking in Compose belgelerinde belirtildiği gibi composable'lar yan efekt içermemelidir. Uygulamanın durumunda değişiklik yapmanız gerektiğinde (Durumu yönetme belgeleri dokümanında açıklandığı gibi) bu yan etkilerin tahmin edilebilir bir şekilde yürütülmesi için Efekt API'lerini kullanmanız gerekir.
Oluşturma'da açılan farklı olasılık efektleri nedeniyle, bu efektler kolayca aşırı kullanılabilir. Bunlarda yaptığınız işin kullanıcı arayüzü ile ilgili olduğundan ve Durumu yönetme belgelerinde açıklandığı gibi tek yönlü veri akışını bozmadığından emin olun.
LaunchedEffect
: composable kapsamındaki askıya alma işlevlerini çalıştırma
Askıya alma işlevlerini bir composable'ın içinden güvenli bir şekilde çağırmak için LaunchedEffect
composable'ı kullanın. LaunchedEffect
Beste bölümüne girdiğinde, parametre olarak geçirilen kod bloğuyla bir eş yordam başlatır. LaunchedEffect
besteden ayrılırsa eş yordam iptal edilir. LaunchedEffect
farklı anahtarlarla yeniden oluşturulursa (aşağıdaki Yeniden Başlatma Efektleri bölümüne bakın) mevcut eş yordam iptal edilir ve yeni askıya alma işlevi yeni bir eş yordamda başlatılır.
Örneğin, Scaffold
içinde bir Snackbar
gösterme işlemi askıya alma işlevi olan SnackbarHostState.showSnackbar
işleviyle yapılır.
@Composable fun MyScreen( state: UiState<List<Movie>>, snackbarHostState: SnackbarHostState ) { // If the UI state contains an error, show snackbar if (state.hasError) { // `LaunchedEffect` will cancel and re-launch if // `scaffoldState.snackbarHostState` changes LaunchedEffect(snackbarHostState) { // Show snackbar using a coroutine, when the coroutine is cancelled the // snackbar will automatically dismiss. This coroutine will cancel whenever // `state.hasError` is false, and only start when `state.hasError` is true // (due to the above if-check), or if `scaffoldState.snackbarHostState` changes. snackbarHostState.showSnackbar( message = "Error message", actionLabel = "Retry message" ) } } Scaffold( snackbarHost = { SnackbarHost(hostState = snackbarHostState) } ) { contentPadding -> // ... } }
Yukarıdaki kodda, durum bir hata içeriyorsa eş yordam tetiklenir,
hata yoksa işlem iptal edilir. LaunchedEffect
çağrısı sitesi if ifadesi içinde yer aldığından, ifade yanlış olduğunda LaunchedEffect
Bestede yer alıyorsa bu ifade kaldırılır ve eş yordam iptal edilir.
rememberCoroutineScope
: composable dışında bir eş yordam başlatmak için besteye duyarlı bir kapsam elde etme
LaunchedEffect
, composable bir işlev olduğundan yalnızca diğer composable işlevlerin içinde kullanılabilir. composable'ın dışında bir eş yordam başlatmak, ancak besteden ayrıldığında otomatik olarak iptal edilecek şekilde kapsamını ayarlamak için rememberCoroutineScope
değerini kullanın.
Bir veya daha fazla eşin yaşam döngüsünü manuel olarak kontrol etmeniz gerektiğinde (örneğin, bir kullanıcı etkinliği gerçekleştiğinde bir animasyonu iptal etmek) rememberCoroutineScope
de kullanabilirsiniz.
rememberCoroutineScope
, Beste'nin çağrıldığı noktaya bir CoroutineScope
bağlı değeri döndüren composable bir işlevdir. Çağrı, Besteden ayrıldığında kapsam iptal edilir.
Önceki örneğe göre, kullanıcı Button
öğesine dokunduğunda bir Snackbar
göstermek için bu 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ğer değişirse yeniden başlatılmaması gereken bir efektteki değere başvuruda bulunun
LaunchedEffect
, temel parametrelerden biri değiştiğinde yeniden başlatılır. Ancak bazı durumlarda, etkisinize bir değer kaydetmek isteyebilirsiniz. Bu değer, değişirse etkinin yeniden başlamasını istemezsiniz. Bunu yapmak için rememberUpdatedState
kullanarak bu değere yakalanıp güncellenebilecek bir referans oluşturmanız gerekir. Bu yaklaşım, yeniden başlatılması pahalı veya yasaklayıcı olabilecek uzun süreli işlemler içeren etkiler için faydalıdır.
Örneğin, uygulamanızda bir süre sonra kaybolan bir LandingScreen
bulunduğunu varsayalım. LandingScreen
yeniden derlenmiş olsa bile biraz bekleyip geçen sürenin yeniden başlatılmaması gerektiğini bildiren efekt:
@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 */ }
Çağrı sitesinin yaşam döngüsüyle eşleşen bir efekt oluşturmak için parametre olarak Unit
veya true
gibi asla değişmeyen bir sabit değer iletilir. Yukarıdaki kodda LaunchedEffect(true)
kullanılmıştır. onTimeout
lambda'nın her zaman LandingScreen
yeniden derlenen en son değeri içerdiğinden emin olmak için onTimeout
işlevinin rememberUpdatedState
işleviyle sarmalanması gerekir.
Döndürülen State
(koddaki currentOnTimeout
), efektte kullanılmalıdır.
DisposableEffect
: Temizlik gerektiren efektler
Tuşlar değiştikten sonra temizlenmesi gereken yan etkiler veya composable, Besteden ayrılırsa DisposableEffect
değerini kullanın.
DisposableEffect
anahtarları değişirse composable'ın mevcut efektini silmesi (temizliği yapması) ve efekti tekrar çağırarak sıfırlanması gerekir.
Örneğin, LifecycleObserver
kullanarak Lifecycle
etkinliklerine dayalı analiz etkinlikleri göndermek isteyebilirsiniz.
Compose'da bu etkinlikleri dinlemek için DisposableEffect
kullanarak gözlemcinin kaydını gerektiğinde 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, bu işlem observer
öğesini lifecycleOwner
öğesine ekler. lifecycleOwner
değişirse etki kaldırılır ve yeni lifecycleOwner
ile yeniden başlatılır.
DisposableEffect
, kod bloğunda son ifade olarak onDispose
ifadesini içermelidir. Aksi takdirde, IDE, derleme zamanı hatası görüntüler.
SideEffect
: Oluşturma durumunu Oluşturma olmayan kodda yayınla
Oluşturma durumunu, oluşturma tarafından yönetilmeyen nesnelerle paylaşmak için SideEffect
composable'ı kullanın. SideEffect
kullanmak, etkinin her başarılı yeniden oluşturma işleminden sonra uygulanacağını garanti eder. Diğer yandan, doğrudan bir composable'a efekt yazarken başarılı bir yeniden kompozisyon elde edilmeden önce bir efekt gerçekleştirmek yanlıştır.
Örneğin, analiz kitaplığınız sonraki tüm analiz etkinliklerine özel meta veriler ("bu örnekte kullanıcı özellikleri") ekleyerek kullanıcı popülasyonunuzu segmentlere ayırmanıza olanak tanıyabilir. Mevcut kullanıcının kullanıcı türünü analiz kitaplığınıza iletmek için SideEffect
değerini kullanarak değerini güncelleyin.
@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şturulma dışı durumu Oluştur durumuna dönüştürme
produceState
, değerleri döndürülen bir State
öğesine aktarabilen Beste kapsamındaki bir eş yordamı başlatır. Oluşturma dışı durumu E-posta Yaz durumuna dönüştürmek için (örneğin Flow
, LiveData
veya RxJava
gibi abonelik odaklı harici durumları Beste'ye eklemek) bunu kullanın.
Yapımcı, produceState
Besteye girdiğinde başlatılır ve Beste'den çıktığında iptal edilir. Döndürülen State
birleşir. Aynı değerin ayarlanması yeniden oluşturma işlemini tetiklemez.
produceState
eş yordam oluştursa da, harcama yapmayan veri kaynaklarını gözlemlemek için de kullanılabilir. Bu kaynağın aboneliğini kaldırmak için awaitDispose
işlevini kullanın.
Aşağıdaki örnekte, ağdan bir resim yüklemek için produceState
özelliğinin nasıl kullanılacağı gösterilmektedir. loadNetworkImage
composable işlevi, diğer composable'larda 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ürün
Compose'da gözlemlenen bir durum nesnesi veya composable giriş her değiştiğinde yeniden oluşturma işlemi gerçekleşir. Bir durum nesnesi veya girişi, kullanıcı arayüzünün gerçekten güncellenmesi gerekenden daha sık değişerek gereksiz yeniden yapılandırmaya yol açabilir.
Bir composable'a girişleriniz yeniden oluşturmanız gerekenden daha sık değişiyorsa derivedStateOf
işlevini kullanmalısınız. Bu, genellikle kaydırma konumu gibi bir öğe sık sık değiştiğinde ancak composable'ın yalnızca belirli bir eşiği aştığında buna tepki vermesi gerektiğinde ortaya çıkar. derivedStateOf
, gözlemleyebileceğiniz yeni bir Compose durumu nesnesi oluşturur. Bu nesne yalnızca ihtiyacınız kadar güncellenir. Bu sayede, 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, görünür ilk öğe her değiştiğinde firstVisibleItemIndex
değişir. Kaydırdığınızda değer 0
, 1
, 2
, 3
, 4
, 5
vb. olur.
Bununla birlikte, yeniden oluşturma işleminin yalnızca değer 0
değerinden 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
İki Compose durum nesnesini birleştirdiğinizde "durumu türettiğiniz" için derivedStateOf
kullanmanız gerektiğini varsaymak sık yapılan bir hatadır. Ancak bu, aşağıdaki snippet'te gösterildiği gibi tamamen genel bir işlemdir 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
de firstName
ve lastName
kadar sık güncellenmelidir. Bu nedenle, aşırı bir yeniden oluşturma işlemi gerçekleşmez ve derivedStateOf
kullanılması gerekmez.
snapshotFlow
: Oluştur'un Durumunu Akışlara dönüştürün
State<T>
nesnelerini bir soğuk Akışa dönüştürmek için snapshotFlow
aracını kullanın. snapshotFlow
, toplandığında bloğunu çalıştırır ve içinde okunan State
nesnelerinin sonucunu çıkarır. snapshotFlow
bloğunun içinde okunan State
nesnelerinden biri değiştiğinde, yeni değer önceki yayınlanan değerle eşit değilse Akış, yeni değeri toplayıcıya yayınlar (bu davranış Flow.distinctUntilChanged
karakterine benzer).
Aşağıdaki örnekte, kullanıcı Analytics'e erişmek için bir listedeki ilk öğeyi geçtikten sonra bunu kaydeden 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 yararlanan bir Akışa dönüştürülür.
Yeniden başlatma efektleri
Oluşturma'daki LaunchedEffect
, produceState
veya DisposableEffect
gibi bazı efektler, çalışan efekti iptal etmek ve yeni anahtarlarla yeni bir efekt başlatmak için kullanılan değişken sayıda bağımsız değişken veya anahtar kullanır.
Bu API'lerin tipik biçimi:
EffectName(restartIfThisKeyChanges, orThisKey, orThisKey, ...) { block }
Bu davranışın inceliklerinden dolayı, efekti yeniden başlatmak için kullanılan parametreler doğru değilse sorunlar oluşabilir:
- Yeniden başlatmanın olması gerekenden daha az etkileri, uygulamanızda hatalara neden olabilir.
- Yeniden başlatmanın olması gerekenden fazla etkileri olması verimsiz olabilir.
Genel kural olarak, efekt blokunda kullanılan değişken ve sabit değişkenler, composable efektine parametre olarak eklenmelidir. Efektin yeniden başlatılmasını sağlamak için bunların dışında daha fazla parametre eklenebilir. Bir değişkendeki değişiklik, etkinin yeniden başlatılmasına neden olmayacaksa değişken rememberUpdatedState
içine yerleştirilmelidir. Değişken, anahtarı olmadan bir remember
içine sarmalandığı için hiçbir zaman değişmezse değişkeni efekt için anahtar olarak aktarmanız gerekmez.
Yukarıda gösterilen DisposableEffect
kodunda, bu etki, blokunda kullanılan lifecycleOwner
parametresi olarak kabul edilir, çünkü bunlarda 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 Bileşim'de değerleri hiçbir zaman değişmediğinden currentOnStart
ve currentOnStop
, DisposableEffect
anahtarları olarak gerekli değildir. lifecycleOwner
parametresini parametre olarak iletmezseniz ve değiştirirse HomeScreen
yeniden oluşturur ancak DisposableEffect
atılmaz ve yeniden başlatılır. Bu durum, o noktadan itibaren yanlış lifecycleOwner
kullanıldığından sorunlara yol açar.
Tuş olarak sabitler
true
gibi bir sabit değeri kullanarak çağrı sitesinin yaşam döngüsünü takip etmesini sağlayabilirsiniz. Yukarıda gösterilen LaunchedEffect
örneğindeki gibi bunun geçerli kullanım alanları vardır. Ancak bunu yapmadan önce iki kez düşünün
ve ihtiyacınız olan şeyin bu olduğundan emin olun.
Sizin için önerilenler
- Not: Bağlantı metni JavaScript kapalıyken görüntülenir
- State ve Jetpack Compose
- Jetpack Compose için Kotlin
- Oluşturma işleminde görünümleri kullanma