In Jetpack Compose, un oggetto può implementare RememberObserver per ricevere
callback quando viene utilizzato con remember per sapere quando inizia e smette di essere
memorizzato nella gerarchia di composizione. Allo stesso modo, puoi utilizzare
RetainObserver per ricevere informazioni sullo stato di un oggetto utilizzato
con retain.
Per gli oggetti che utilizzano queste informazioni sul ciclo di vita dalla gerarchia di composizione,
consigliamo alcune best practice per verificare che gli oggetti si comportino in modo corretto
nella piattaforma e si difendano dall'uso improprio. In particolare, utilizza i callback
onRemembered (o onRetained) per avviare il lavoro anziché il
costruttore, annulla tutto il lavoro quando gli oggetti non vengono più ricordati o conservati ed
evita di divulgare implementazioni di RememberObserver e RetainObserver per
evitare chiamate accidentali. La sezione successiva spiega questi consigli in modo più
approfondito.
Inizializzazione e pulizia con RememberObserver e RetainObserver
La guida Thinking in Compose descrive il modello mentale alla base
della composizione. Quando lavori con RememberObserver e RetainObserver, è
importante tenere presente due comportamenti della composizione:
- La ricomposizione è ottimistica e potrebbe essere annullata
- Tutte le funzioni componibili non devono avere effetti collaterali
Esegui gli effetti collaterali dell'inizializzazione durante onRemembered o onRetained, non durante la costruzione
Quando gli oggetti vengono ricordati o conservati, la lambda di calcolo viene eseguita nell'ambito della composizione. Per gli stessi motivi per cui non esegui un effetto collaterale o
avvii una coroutine durante la composizione, non devi eseguire effetti collaterali
nella lambda di calcolo passata a remember, retain e alle relative varianti.
Ciò include la parte del costruttore per gli oggetti memorizzati o conservati.
Quando implementi RememberObserver o RetainObserver, verifica che
tutti gli effetti e i job avviati vengano inviati nel callback onRemembered.
Offre la stessa tempistica delle API SideEffect. Garantisce inoltre che
questi effetti vengano eseguiti solo quando viene applicata la composizione, il che impedisce
lavori orfani e perdite di memoria se una ricomposizione viene abbandonata o posticipata.
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() { /* ... */ } // ... }
Smontaggio in caso di dimenticanza, ritiro o abbandono
Per evitare perdite di risorse o l'abbandono di job in background, è necessario eliminare anche gli oggetti memorizzati. Per gli oggetti che implementano RememberObserver,
ciò significa che qualsiasi elemento inizializzato in onRemembered deve avere una chiamata di rilascio corrispondente in onForgotten.
Poiché la composizione può essere annullata, gli oggetti che implementano RememberObserver
devono anche pulire se vengono abbandonati nelle composizioni. Un
oggetto viene abbandonato quando viene restituito da remember in una composizione che viene
annullata o non riesce. (Ciò si verifica più comunemente quando si utilizza PausableComposition e può verificarsi anche quando si utilizza il ricaricamento rapido con gli strumenti di anteprima dei componenti componibili di Android Studio.)
Quando un oggetto memorizzato viene abbandonato, riceve solo la chiamata a
onAbandoned (e nessuna chiamata a onRemembered). Per implementare il metodo di abbandono,
elimina tutto ciò che è stato creato tra l'inizializzazione dell'oggetto e il momento in cui
l'oggetto avrebbe ricevuto il callback onRemembered.
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() } }
Mantenere private le implementazioni di RememberObserver e RetainObserver
Quando scrivi API pubbliche, fai attenzione quando estendi RememberObserver e
RetainObserver nella creazione di classi restituite pubblicamente. Un utente potrebbe non
ricordare il tuo oggetto quando ti aspetti che lo faccia o potrebbe ricordarlo in un
modo diverso da quello che intendevi. Per questo motivo, ti consigliamo di non esporre
costruttori o funzioni di fabbrica per oggetti che implementano RememberObserver
o RetainObserver. Tieni presente che questo dipende dal tipo di runtime di una classe,
non dal tipo dichiarato. Ricordare un oggetto che implementa RememberObserver
o RetainObserver, ma viene eseguito il cast a Any, fa comunque sì che l'oggetto riceva
callback.
Opzione non consigliata:
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()
Consigliato:
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 }
Considerazioni quando si memorizzano oggetti
Oltre ai consigli precedenti relativi a RememberObserver e
RetainObserver, ti consigliamo anche di prestare attenzione ed evitare di
ricordare accidentalmente gli oggetti sia per le prestazioni che per la correttezza. Le sezioni seguenti approfondiscono scenari specifici di riassegnazione e il motivo per cui devono essere evitati.
Ricorda gli oggetti una sola volta
Ricordare di nuovo un oggetto può essere pericoloso. Nel migliore dei casi, potresti sprecare risorse per ricordare un valore già memorizzato. Tuttavia, se un
oggetto implementa RememberObserver e viene ricordato due volte inaspettatamente, riceverà più callback del previsto. Ciò può causare problemi, in quanto la logica di onRemembered e onForgotten verrà eseguita due volte e la maggior parte delle implementazioni di RememberObserver non supporta questo caso. Se una seconda
chiamata remember viene eseguita in un ambito diverso con una durata diversa da
remember originale, molte implementazioni di RememberObserver.onForgotten
eliminano l'oggetto prima che il suo utilizzo sia terminato.
val first: RememberObserver = rememberFoo()
// Not Recommended: Re-remembered `Foo` now gets double callbacks
val second = remember { first }
Questi suggerimenti non si applicano agli oggetti che vengono ricordati di nuovo in modo transitivo (ad esempio, gli oggetti ricordati che utilizzano un altro oggetto ricordato). È comune scrivere codice come segue, il che è consentito perché viene ricordato un oggetto diverso e quindi non causa un raddoppio imprevisto del callback.
val foo: Foo = rememberFoo() // Acceptable: val bar: Bar = remember { Bar(foo) } // Recommended key usage: val barWithKey: Bar = remember(foo) { Bar(foo) }
Supponi che gli argomenti della funzione siano già memorizzati
Una funzione non deve memorizzare nessuno dei suoi parametri sia perché può portare a
doppie chiamate di callback per RememberObserver sia perché è
inutile. Se un parametro di input deve essere ricordato, verifica che non implementi RememberObserver o richiedi ai chiamanti di ricordare l'argomento.
@Composable
fun MyComposable(
parameter: Foo
) {
// Not Recommended: Input should be remembered by the caller.
val rememberedParameter = remember { parameter }
}
Ciò non si applica agli oggetti ricordati in modo transitivo. Quando memorizzi un
oggetto derivato dagli argomenti di una funzione, valuta la possibilità di specificarlo come una delle
chiavi di remember:
@Composable fun MyComposable( parameter: Foo ) { // Acceptable: val derivedValue = remember { Bar(parameter) } // Also Acceptable: val derivedValueWithKey = remember(parameter) { Bar(parameter) } }
Non conservare un oggetto già memorizzato
Come per il ricordo di un oggetto, dovresti evitare di conservare un oggetto
ricordato per cercare di prolungarne la durata. Questo è il risultato del consiglio riportato in
Durate degli stati: retain non deve essere utilizzato con oggetti che hanno una
durata che non corrisponde a quella delle offerte di conservazione. Poiché gli oggetti remembered
hanno una durata inferiore rispetto agli oggetti retained, non devi conservare un
oggetto ricordato. Preferisci invece conservare l'oggetto nel sito di origine
anziché memorizzarlo.