RememberObserver ve RetainObserver ile Compose durum geri çağırmalarını oluşturma

Jetpack Compose'da bir nesne, RememberObserver'ı uygulayarak remember ile kullanıldığında geri çağırma işlemleri alabilir. Böylece, kompozisyon hiyerarşisinde ne zaman hatırlanmaya başlandığını ve ne zaman hatırlanmayı bıraktığını öğrenebilir. Benzer şekilde, retain ile kullanılan bir nesnenin durumu hakkında bilgi almak için RetainObserver kullanabilirsiniz.

Bileşim hiyerarşisindeki bu yaşam döngüsü bilgilerini kullanan nesneler için, nesnelerinizin platformda iyi bir şekilde çalıştığını ve kötüye kullanıma karşı korunduğunu doğrulamak amacıyla birkaç en iyi uygulamayı kullanmanızı öneririz. Özellikle, işi başlatmak için oluşturucu yerine onRemembered (veya onRetained) geri çağırmalarını kullanın, nesneler hatırlanmayı veya saklanmayı bıraktığında tüm işleri iptal edin ve yanlışlıkla çağrılmaları önlemek için RememberObserver ve RetainObserver uygulamalarının sızdırılmasını önleyin. Sonraki bölümde bu öneriler daha ayrıntılı olarak açıklanmaktadır.

RememberObserver ve RetainObserver ile başlatma ve temizleme

Compose'da düşünme kılavuzunda, kompozisyonun arkasındaki zihinsel model açıklanmaktadır. RememberObserver ve RetainObserver ile çalışırken kompozisyonun iki davranışını göz önünde bulundurmak önemlidir:

  • Yeniden oluşturma işlemi iyimserdir ve iptal edilebilir.
  • Tüm composable işlevler yan etkilere sahip olmamalıdır.

Yapılandırma sırasında değil, onRemembered veya onRetained sırasında ilk kullanıma hazırlama yan etkilerini çalıştırın.

Nesneler hatırlandığında veya saklandığında hesaplama lambda'sı kompozisyonun bir parçası olarak çalışır. Kompozisyon sırasında yan etki oluşturmamanız veya eşzamanlı rutin başlatmamanız için geçerli olan nedenlerle, remember, retain ve bunların varyasyonlarına iletilen hesaplama lambda'sında da yan etki oluşturmamalısınız. Bu, hatırlanan veya saklanan nesnelerin oluşturucusu kapsamında yer alır.

Bunun yerine, RememberObserver veya RetainObserver uygulanırken tüm efektlerin ve başlatılan işlerin onRemembered geri çağırma işlevinde gönderildiğini doğrulayın. Bu, SideEffect API'leriyle aynı zamanlamayı sunar. Ayrıca, bu efektlerin yalnızca kompozisyon uygulandığında yürütülmesini sağlayarak yeniden oluşturma işlemi bırakılırsa veya ertelenirse yetim işlerin ve bellek sızıntılarının oluşmasını önler.

class MyComposeObject : RememberObserver {
    private val job = Job()
    private val coroutineScope = CoroutineScope(Dispatchers.Main + job)

    init {
        // Not recommended: This will cause work to begin during composition instead of
        // with other effects. Move this into onRemembered().
        coroutineScope.launch { loadData() }
    }

    override fun onRemembered() {
        // Recommended: Move any cancellable or effect-driven work into the onRemembered
        // callback. If implementing RetainObserver, this should go in onRetained.
        coroutineScope.launch { loadData() }
    }

    private suspend fun loadData() { /* ... */ }

    // ...
}

Unutulduğunda, kullanımdan kaldırıldığında veya terk edildiğinde sökme

Kaynak sızıntısını önlemek veya arka plan işlerinin yetim kalmasını engellemek için hatırlanan nesneler de bertaraf edilmelidir. RememberObserver öğesini uygulayan nesneler için bu, onRemembered içinde başlatılan her şeyin onForgotten içinde eşleşen bir yayınlama çağrısına sahip olması gerektiği anlamına gelir.

Birleştirme işlemi iptal edilebildiğinden, RememberObserver uygulayan nesneler, birleştirme işlemlerinde bırakılırlarsa kendilerini de temizlemelidir. Bir nesne, iptal edilen veya başarısız olan bir kompozisyonda remember tarafından döndürüldüğünde terk edilmiş olur. (Bu durum en sık PausableComposition kullanılırken görülür. Ayrıca Android Studio'nun composable önizleme araçlarıyla anında yeniden yükleme kullanılırken de ortaya çıkabilir.)

Hatırlanan bir nesne terk edildiğinde yalnızca onAbandoned işlevine yapılan çağrıyı alır (onRemembered işlevine yapılan çağrıyı almaz). Terk etme yöntemini uygulamak için nesne başlatıldığında ve nesne onRemembered geri çağırmasını aldığında oluşturulan her şeyi silin.

class MyComposeObject : RememberObserver {
    private val job = Job()
    private val coroutineScope = CoroutineScope(Dispatchers.Main + job)

    // ...

    override fun onForgotten() {
        // Cancel work launched from onRemembered. If implementing RetainObserver, onRetired
        // should cancel work launched from onRetained.
        job.cancel()
    }

    override fun onAbandoned() {
        // If any work was launched by the constructor as part of remembering the object,
        // you must cancel that work in this callback. For work done as part of the construction
        // during retain, this code should will appear in onUnused.
        job.cancel()
    }
}

RememberObserver ve RetainObserver uygulamalarını gizli tutma

Herkese açık API'ler yazarken, herkese açık olarak döndürülen sınıflar oluşturmada RememberObserver ve RetainObserver öğelerini genişletirken dikkatli olun. Kullanıcılar, nesnenizi hatırlamalarını beklediğinizde hatırlamayabilir veya nesnenizi sizin istediğinizden farklı bir şekilde hatırlayabilir. Bu nedenle, RememberObserver veya RetainObserver uygulayan nesneler için oluşturucuları ya da fabrika işlevlerini kullanıma sunmamanızı öneririz. Bunun, bildirilen türden değil, bir sınıfın çalışma zamanı türüne bağlı olduğunu unutmayın. RememberObserver veya RetainObserver uygulayan ancak Any olarak yayınlanan bir nesnenin hatırlanması, nesnenin geri çağırma işlemleri almasına neden olmaya devam eder.

Önerilmeyenler:

abstract class MyManager

// Not Recommended: Exposing a public constructor (even implicitly) for an object implementing
// RememberObserver can cause unexpected invocations if it is remembered multiple times.
class MyComposeManager : MyManager(), RememberObserver { ... }

// Not Recommended: The return type may be an implementation of RememberObserver and should be
// remembered explicitly.
fun createFoo(): MyManager = MyComposeManager()

Önerilen:

abstract class MyManager

class MyComposeManager : MyManager() {
    // Callers that construct this object must manually call initialize and teardown
    fun initialize() { /*...*/ }
    fun teardown() { /*...*/ }
}

@Composable
fun rememberMyManager(): MyManager {
    // Protect the RememberObserver implementation by never exposing it outside the library
    return remember {
        object : RememberObserver {
            val manager = MyComposeManager()
            override fun onRemembered() = manager.initialize()
            override fun onForgotten() = manager.teardown()
            override fun onAbandoned() { /* Nothing to do if manager hasn't initialized */ }
        }
    }.manager
}

Nesneleri hatırlarken dikkat edilmesi gereken noktalar

RememberObserver ve RetainObserver ile ilgili önceki önerilere ek olarak, hem performans hem de doğruluk açısından nesneleri yanlışlıkla yeniden hatırlamaktan kaçınmanızı ve bu konuda dikkatli olmanızı öneririz. Aşağıdaki bölümlerde, belirli yeniden hatırlama senaryoları ve neden bunlardan kaçınılması gerektiği daha ayrıntılı olarak açıklanmaktadır.

Nesneleri yalnızca bir kez hatırla

Bir nesneyi yeniden hatırlamak tehlikeli olabilir. En iyi senaryoda, zaten hatırlanan bir değeri hatırlayarak kaynakları boşa harcıyor olabilirsiniz. Ancak bir nesne RememberObserver uyguluyorsa ve beklenmedik bir şekilde iki kez hatırlanıyorsa beklediğinden daha fazla geri çağırma alır. onRemembered ve onForgotten mantığı iki kez yürütüleceğinden ve RememberObserver'nin çoğu uygulaması bu durumu desteklemediğinden bu durum sorunlara neden olabilir. Orijinal remember ile farklı bir yaşam süresine sahip farklı bir kapsamda ikinci bir remember() çağrısı gerçekleşirse RememberObserver.onForgotten'nin birçok uygulaması, nesneyi kullanılmadan önce siler.

val first: RememberObserver = rememberFoo()

// Not Recommended: Re-remembered `Foo` now gets double callbacks
val second = remember { first }

Bu tavsiye, dolaylı olarak tekrar hatırlanan nesneler (ör. başka bir hatırlanan nesneyi tüketen hatırlanan nesneler) için geçerli değildir. Aşağıdaki gibi görünen kod yazmak yaygındır. Farklı bir nesne hatırlandığı için bu koda izin verilir ve beklenmedik geri çağırma çiftlenmesine neden olmaz.

val foo: Foo = rememberFoo()

// Acceptable:
val bar: Bar = remember { Bar(foo) }

// Recommended key usage:
val barWithKey: Bar = remember(foo) { Bar(foo) }

İşlev bağımsız değişkenlerinin zaten hatırlandığını varsay

Bir işlev, hem RememberObserver için çift geri çağırma çağırmasına yol açabileceğinden hem de gereksiz olduğundan parametrelerini hatırlamamalıdır. Bir giriş parametresinin hatırlanması gerekiyorsa RememberObserver uygulamadığını doğrulayın veya arayanların bağımsız değişkenlerini hatırlamalarını zorunlu kılın.

@Composable
fun MyComposable(
    parameter: Foo
) {
    // Not Recommended: Input should be remembered by the caller.
    val rememberedParameter = remember { parameter }
}

Bu, geçişli olarak hatırlanan nesneler için geçerli değildir. Bir işlevin bağımsız değişkenlerinden türetilen bir nesneyi hatırlarken bunu remember anahtarlarından biri olarak belirtmeyi düşünün:

@Composable
fun MyComposable(
    parameter: Foo
) {
    // Acceptable:
    val derivedValue = remember { Bar(parameter) }

    // Also Acceptable:
    val derivedValueWithKey = remember(parameter) { Bar(parameter) }
}

Zaten hatırlanan bir nesneyi saklamayın

Bir nesneyi yeniden hatırlamaya benzer şekilde, ömrünü uzatmak için hatırlanan bir nesneyi saklamaktan kaçınmalısınız. Bu, Durum ömürleri: retain, elde tutma tekliflerinin ömrüyle eşleşmeyen bir ömre sahip nesnelerle kullanılmamalıdır başlıklı makaledeki tavsiyenin bir sonucudur. remembered nesnelerinin kullanım ömrü retained nesnelerinden daha kısa olduğundan hatırlanan nesneleri saklamamalısınız. Bunun yerine, nesneyi hatırlamak yerine kaynak sitede tutmayı tercih edin.