remember — ключевые понятия, сохранение значений между рекомпозициями

Автор: IT Sectr Опубликовано: 2026-06-28 Время чтения: 8 мин

remember — это функция в Jetpack Compose, которая позволяет сохранять значение между рекомпозициями, предотвращая сброс состояния при каждом перезапуске Composable-функции. Без remember все переменные, объявленные внутри функции, обнулялись бы при каждом обновлении UI, делая состояние нестабильным и бесполезным. По данным Google Android Developers, 2026, правильное использование remember является основой корректного управления состоянием в декларативном UI.

Главное

  • remember сохраняет значение в слоте композиции между рекомпозициями функции
  • Ключи в remember(key) определяют, когда значение должно быть пересчитано заново
  • rememberSaveable сохраняет значение при повороте экрана и сворачивании приложения
  • derivedStateOf создаёт производное состояние, оптимизируя вычисления при рекомпозиции
  • Сброс remember происходит только при выходе функции из состава композиции

Что такое remember в Jetpack Compose

remember — это встроенная функция Jetpack Compose из пакета androidx.compose.runtime, которая создаёт объект, сохраняющий своё значение между последовательными вызовами Composable-функции. Технически remember работает со слотами — специальными ячейками памяти, выделенными для каждой позиции вызова функции в UI-дереве. Пока функция остаётся в составе композиции (не удалена через if-условие), remember будет возвращать сохранённое значение при каждой рекомпозиции.

Синтаксис remember прост: внутри фигурных скобок указывается блок вычисления начального значения. Блок выполняется только один раз — при первой композиции функции. Все последующие рекомпозиции возвращают уже сохранённое значение, не вызывая блок заново. Однако если указан ключ (параметр key), блок перевыполняется при изменении ключа, что позволяет пересчитывать значение на основе входных данных.

remember не является магией Kotlin — это простая функция с параметром key и лямбдой вычисления. Её внутренняя реализация использует Composer для чтения и записи в слоты. Компилятор Kotlin, видя вызов remember, генерирует код, который обращается к текущему CompositionContext и менеджерити слоты. Это означает, что remember можно вызывать только внутри Composable-функции или другой функции в контексте композиции.

Виды remember: remember, rememberSaveable, derivedStateOf

В Jetpack Compose существует три основных варианта remember, каждый со своим назначением. Базовый remember сохраняет значение только в памяти текущего процесса — при повороте экрана (config change) Compose пересоздаёт Composition, и все remember-значения сбрасываются. Для сохранения данных при конфигурационных изменениях и остановке процесса используется rememberSaveable.

rememberSaveable сериализует значение в Bundle через механизм SavedStateHandle или Parcelable. Это позволяет пережить поворот экрана, вызов "Не сохранять активности" и даже временное сворачивание приложения. Однако rememberSaveable накладывает ограничения на тип сохраняемых данных: они должны быть примитивами, Parcelable, Serializable или поддерживать Saver-конвертер.

derivedStateOf — это не механизм сохранения, а оптимизация. Он создаёт State, значение которого вычисляется на основе других State-объектов. derivedStateOf реагирует на изменения исходных состояний, но пересчитывает значение только при наличии подписчиков. Если текущая рекомпозиция не читает derivedStateOf, вычисление не выполняется, что экономит ресурсы при частых, но никому не нужных обновлениях.

kotlin
@Composable
fun RememberVariants() {
    // 1. Basic remember: persists until leaving composition
    val createdAt = remember { System.currentTimeMillis() }

    // 2. rememberSaveable: survives screen rotation
    var username by rememberSaveable { mutableStateOf("") }

    // 3. derivedStateOf: computed only when needed
    val isButtonVisible = remember {
        derivedStateOf { username.length() > 3 }
    }

    Text("Created: $createdAt")
    TextField(value = username, onValueChange = { username = it })

    if (isButtonVisible.value) {
        Text("Button will be shown")
    }
}

remember vs rememberSaveable: когда что использовать

Выбор между remember и rememberSaveable зависит от того, насколько критично сохранение данных при конфигурационных изменениях. Для временных состояний, которые неважно терять при повороте (анимация, текущая позиция скролла, состояние фокуса) — достаточно обычного remember. Для критичных данных (текст в форме, выбранные элементы, чекбоксы) — используйте rememberSaveable.

Производительность rememberSaveable ниже, чем у обычного remember, так как требует сериализации в Bundle. Делайте rememberSaveable только для данных, которые действительно должны пережить пересоздание Activity. Для всего остального — простой remember. Избыточное использование rememberSaveable приводит к замедлению при поворотах экрана и при переключении между приложениями.

Если вы работаете с классами, не поддерживающими Parcelable или Serializable, используйте Saver — конвертер, который определяет, как сохранять и восстанавливать объект. Saver описывается парой лямбд: save (преобразует объект в сохраняемый тип) и restore (восстанавливает объект из сохранённых данных). Стандартный Saver уже реализован для mutableStateListOf и mutableStateMapOf.

ХарактеристикаrememberrememberSaveable
Сохранение при рекомпозицииДаДа
Сохранение при поворотеНетДа
Сохранение при остановке процессаНетДа
Требования к типуЛюбойParcelable, Serializable, Saver
ПроизводительностьВысокаяСредняя

Примеры использования remember в реальных проектах

Рассмотрим типовой сценарий — экран редактирования профиля, где remember используется для нескольких целей: хранения состояния полей формы, расчёта производных значений и кэширования вычислительных затратных операций.

kotlin
data class ProfileUiState(
    val name: String = "",
    val bio: String = "",
    val isSaving: Boolean = false
)

@Composable
fun ProfileEditor() {
    var state by rememberSaveable { mutableStateOf(ProfileUiState()) }
    val isValid = remember {
        derivedStateOf { state.name.isNotBlank() && state.bio.length() <= 500 }
    }

    val bioWarning = remember(state.bio) {
        if (state.bio.length() > 400) {
            "${500 - state.bio.length} chars left"
        } else null
    }

    Column(modifier = Modifier.padding(16.dp)) {
        OutlinedTextField(
            value = state.name,
            onValueChange = { state = state.copy(name = it) },
            label = { Text("Name") }
        )

        OutlinedTextField(
            value = state.bio,
            onValueChange = { state = state.copy(bio = it) },
            label = { Text("About") }
        )

        bioWarning?.let { Text(it, color = MaterialTheme.colorScheme.error) }

        Button(onClick = { /* save */ },
            enabled = isValid.value) {
            Text("Save")
        }
    }
}

В примере state сохраняется через rememberSaveable — при повороте экрана текст не пропадёт. isValid вычисляется через derivedStateOf, что предотвращает лишние вычисления при рекомпозициях. bioWarning использует remember с ключом bio — это дорогое вычисление (опционально, для демонстрации), которое пересчитывается только при изменении bio, а не при каждой рекомпозиции.

Производные состояния и remember с ключами

Производные состояния (derived state) — это значения, которые вычисляются из других State-объектов. Вместо того чтобы вычислять их при каждой рекомпозиции и тратить CPU на идентичные результаты, remember с derivedStateOf вычисляет значение только при изменении источников. Это особенно полезно для фильтрации, сортировки и агрегации данных.

remember с ключами (remember(key) { calculation }) — другой механизм оптимизации. Если ключ не изменился с прошлой рекомпозиции, блок вычисления не выполняется, и возвращается закэшированное значение. Это удобно для кэширования объектов, создание которых дорого: форматирование дат, парсинг JSON, создание больших неизменяемых коллекций.

kotlin
@Composable
fun SearchResults(allItems: List<Item>, query: String) {
    // derivedStateOf: filter recomputed only when inputs change
    val filtered = remember {
        derivedStateOf {
            allItems.filter { it.title.contains(query, true) }
        }
    }

    // remember with key: formatted stats recomputed only on query change
    val statsText = remember(query) {
        "Results for query \"$query\": ${filtered.value.size}"
    }

    Text(statsText)
    LazyColumn {
        items(filtered.value, key = { it.id }) { item ->
            Text(item.title)
        }
    }
}

filtered — это derivedStateOf, который автоматически пересчитывается при изменении allItems или query. statsText использует remember(query) — дорогое строковое форматирование выполняется только при смене поискового запроса. Комбинация derivedStateOf и remember с ключами даёт максимальную производительность: производное состояние вычисляется только когда нужно, а сложные объекты кэшируются до изменения ключа.

Часто задаваемые вопросы

Можно ли использовать remember вне Composable-функции?

Нет, remember — это функция из пакета compose.runtime, которая требует CompositionContext. Она может быть вызвана только внутри @Composable-функции или внутри другой функции, вызванной из Composable. Для сохранения данных вне композиции используйте ViewModel.

Что произойдёт, если не указать ключ в remember?

Без ключа remember вычисляет блок только один раз — при первой композиции. Все последующие рекомпозиции возвращают сохранённое значение. Если нужно пересчитывать значение при изменении данных, обязательно укажите их в качестве ключа: remember(data) { compute(data) }.

Как сбросить remember принудительно?

Прямого сброса remember нет. Единственный способ — вывести функцию из состава композиции (например, скрыв через if-условие) и затем показать снова. При повторном входе блок remember выполнится заново, создав новое начальное значение.

Чем отличается remember от ViewModel?

remember хранит состояние в слоте Composable-функции и живёт, пока функция в композиции. ViewModel живёт, пока жив lifecycle экрана. ViewModel сохраняется при повороте и используется для бизнес-логики. remember — для локального UI-состояния, которое не нужно за пределами одной функции.

Работает ли remember в Preview-компонентах?

Да, remember корректно работает в @Preview Composable-функциях, так как Preview создаёт полноценный CompositionContext. Однако rememberSaveable может не сработать корректно в Preview, так как SavedStateHandle может отсутствовать в окружении превью.

Итоги

  • remember сохраняет значение в слоте композиции и переживает рекомпозиции функции
  • rememberSaveable дополнительно сохраняет значение при повороте экрана и остановке процесса
  • derivedStateOf создаёт производное состояние с ленивыми вычислениями
  • Ключи в remember(key) управляют пересчётом кэшируемого значения
  • Сброс remember происходит только при удалении функции из композиции
  • Производительность: derivedStateOf и ключи предотвращают лишние вычисления при рекомпозиции
  • Рекомендация: используйте rememberSaveable для полей ввода и remember + derivedStateOf для вычисляемых значений

Мы разработаем мобильное приложение под ключ

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

Читайте также