Создавайте обратные вызовы состояния с помощью RememberObserver и RetainObserver.

В Jetpack Compose объект может реализовывать RememberObserver , чтобы получать обратные вызовы при использовании с remember и знать, когда он начинает и перестает запоминаться в иерархии композиции. Аналогично, можно использовать RetainObserver для получения информации о состоянии объекта, используемого с retain .

Для объектов, использующих информацию о жизненном цикле из иерархии композиции, мы рекомендуем несколько лучших практик для проверки того, что ваши объекты ведут себя корректно на платформе, и защиты от неправомерного использования. В частности, используйте коллбэки onRemembered (или onRetained ) для запуска работы вместо конструктора, отменяйте всю работу, когда объекты перестают запоминаться или сохраняться, и избегайте утечки реализаций RememberObserver и RetainObserver , чтобы предотвратить случайные вызовы. В следующем разделе эти рекомендации объясняются более подробно.

Инициализация и очистка с помощью RememberObserver и RetainObserver

В руководстве «Мышление в процессе создания текста» описывается ментальная модель, лежащая в основе композиции. При работе с RememberObserver и RetainObserver важно помнить о двух аспектах композиции:

  • Перегруппировка оптимистична и может быть отменена.
  • Все компонуемые функции не должны иметь побочных эффектов.

Выполнение побочных эффектов инициализации происходит во время onRemembered или onRetained , а не при создании объекта.

Когда объекты запоминаются или сохраняются, вычислительная лямбда-функция выполняется как часть композиции. По тем же причинам, по которым вы не стали бы выполнять побочные эффекты или запускать сопрограммы во время композиции, вы также не должны выполнять побочные эффекты в вычислительной лямбда-функции, передаваемой функциям remember , retain и их вариациям. Это включает в себя и выполнение в конструкторе для запоминаемых или сохраняемых объектов.

Вместо этого, при реализации RememberObserver или RetainObserver , убедитесь, что все эффекты и запущенные задания отправляются в коллбэке onRemembered . Это обеспечивает ту же синхронизацию, что и API SideEffect . Это также гарантирует, что эти эффекты выполняются только при применении композиции, что предотвращает появление "осиротевших" заданий и утечки памяти, если рекомпозиция отменяется или откладывается.

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() { /* ... */ }

    // ...
}

Демонтаж после того, как что-то забыто, выведено из эксплуатации или заброшено.

Во избежание утечки ресурсов или оставления фоновых задач без присмотра, запомненные объекты также должны быть освобождены. Для объектов, реализующих интерфейс RememberObserver , это означает, что все, что инициализировано в onRemembered должно иметь соответствующий вызов освобождения в onForgotten .

Поскольку композицию можно отменить, объекты, реализующие интерфейс RememberObserver также должны выполнять очистку после себя, если они были оставлены в композиции. Объект считается оставленным, когда он возвращается объектом remember в композиции, которая была отменена или завершилась неудачей. (Это чаще всего происходит при использовании PausableComposition , а также может происходить при использовании горячей перезагрузки с помощью инструментов предварительного просмотра композиции в Android Studio.)

Когда запомненный объект отменяется, он получает только вызов метода onAbandoned (и не получает вызов метода onRemembered ). Для реализации метода abandon необходимо освободить ресурсы, созданные в промежутке между инициализацией объекта и моментом, когда объект должен был получить обратный вызов 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()
    }
}

Сохраните реализации RememberObserver и RetainObserver в приватном режиме.

При написании публичных API следует проявлять осторожность при расширении классов RememberObserver и RetainObserver , возвращаемых публично. Пользователь может не запомнить ваш объект, когда вы ожидаете, или запомнить его иначе, чем вы предполагали. Поэтому мы рекомендуем не предоставлять конструкторы или фабричные функции для объектов, реализующих RememberObserver или RetainObserver . Обратите внимание, что это зависит от типа класса во время выполнения, а не от объявленного типа — запоминание объекта, реализующего RememberObserver или RetainObserver но приведенного к Any все равно приведет к тому, что объект будет получать обратные вызовы.

Не рекомендуется:

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()

Рекомендуется:

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
}

Факторы, которые следует учитывать при запоминании предметов.

В дополнение к предыдущим рекомендациям относительно RememberObserver и RetainObserver , мы также рекомендуем быть внимательными и избегать случайного повторного запоминания объектов как с точки зрения производительности, так и корректности. В следующих разделах более подробно рассматриваются конкретные сценарии повторного запоминания и причины, по которым их следует избегать.

Запоминайте предметы только один раз.

Повторное запоминание объекта может быть опасным. В лучшем случае вы можете тратить ресурсы на запоминание уже запомненного значения. Но если объект реализует RememberObserver и неожиданно запоминается дважды, он получит больше обратных вызовов, чем ожидает. Это может вызвать проблемы, поскольку логика onRemembered и onForgotten будет выполняться дважды, а большинство реализаций RememberObserver не поддерживают этот случай. Если второй вызов remember происходит в другой области видимости, имеющей другой срок жизни, чем исходный remember , многие реализации RememberObserver.onForgotten освобождают объект до того, как он будет полностью использован.

val first: RememberObserver = rememberFoo()

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

Этот совет не применим к объектам, которые запоминаются повторно транзитивно (то есть, запомненные объекты, которые используют другой запомненный объект). Часто пишут код, который выглядит следующим образом, что допустимо, поскольку запоминается другой объект, и, следовательно, это не приводит к неожиданному удвоению коллбэков.

val foo: Foo = rememberFoo()

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

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

Предполагается, что аргументы функции уже запомнены.

Функция не должна запоминать свои параметры, поскольку это может привести к двойному вызову функции обратного вызова RememberObserver , а также потому, что это излишне. Если необходимо запомнить входной параметр, либо убедитесь, что он не реализует RememberObserver , либо потребуйте от вызывающих функций запоминать свой аргумент.

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

Это не относится к объектам, запоминаемым транзитивно. При запоминании объекта, производного от аргументов функции, рекомендуется указать его в качестве одного из remember ключей:

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

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

Не стоит запоминать предмет, который уже кто-то помнит.

Подобно повторному запоминанию объекта, следует избегать сохранения запомненного объекта с целью продления его срока жизни. Это следствие рекомендаций из раздела «Срок жизни состояний» : retain не следует использовать с объектами, срок жизни которых не соответствует сроку жизни, предлагаемому оператором retain. Поскольку remembered объекты имеют более короткий срок жизни, чем retained , не следует сохранять запомненный объект. Вместо этого предпочтительнее сохранять объект в исходном месте, а не запоминать его.