Состояние в приложении — это любое значение, которое может меняться со временем. Это очень широкое определение, охватывающее всё: от базы данных Room до переменной в классе.
Все приложения Android отображают состояние для пользователя. Вот несколько примеров состояния в приложениях Android:
- Снэк-бар, который отображается, когда сетевое соединение не может быть установлено.
- Запись в блоге и комментарии к ней.
- Анимация ряби на кнопках, которая воспроизводится, когда пользователь нажимает на них.
- Наклейки, которые пользователь может рисовать поверх изображения.
Jetpack Compose поможет вам чётко определить, где и как хранить и использовать состояние в приложении Android. В этом руководстве основное внимание уделено связи между состоянием и компонуемыми объектами, а также API, которые Jetpack Compose предлагает для более удобной работы с состоянием.
Состояние и состав
Compose является декларативным, поэтому единственный способ его обновить — вызвать тот же компонуемый объект с новыми аргументами. Эти аргументы представляют состояние пользовательского интерфейса. При каждом обновлении состояния происходит рекомпозиция . В результате такие объекты, как TextField
, не обновляются автоматически, как в императивных представлениях на основе XML. Для обновления компонуемого объекта необходимо явно указать новое состояние.
@Composable private fun HelloContent() { Column(modifier = Modifier.padding(16.dp)) { Text( text = "Hello!", modifier = Modifier.padding(bottom = 8.dp), style = MaterialTheme.typography.bodyMedium ) OutlinedTextField( value = "", onValueChange = { }, label = { Text("Name") } ) } }
Если вы запустите этот код и попытаетесь ввести текст, вы увидите, что ничего не происходит. Это связано с тем, что TextField
не обновляется само по себе, а обновляется при изменении своего параметра- value
. Это связано с особенностями работы композиции и перекомпоновки в Compose.
Дополнительную информацию о первоначальном сочинении и пересочинении см. в разделе Размышления при сочинении .
Состояние в компонуемых объектах
Компонуемые функции могут использовать API remember
для сохранения объекта в памяти. Значение, вычисленное remember
, сохраняется в Composition во время первоначальной композиции и возвращается во время повторной композиции. remember
может использоваться для хранения как изменяемых, так и неизменяемых объектов.
mutableStateOf
создает наблюдаемый MutableState<T>
, который является наблюдаемым типом, интегрированным со средой выполнения Compose.
interface MutableState<T> : State<T> {
override var value: T
}
Любые изменения value
приводят к перекомпоновке любых составных функций, которые считывают value
.
Существует три способа объявить объект MutableState
в компонуемом объекте:
-
val mutableState = remember { mutableStateOf(default) }
-
var value by remember { mutableStateOf(default) }
-
val (value, setValue) = remember { mutableStateOf(default) }
Эти объявления эквивалентны и предоставляются как синтаксический сахар для различных вариантов использования состояния. Вам следует выбрать тот, который создаёт наиболее удобный для чтения код в создаваемом вами компонуемом объекте.
Синтаксис by
требует следующих импортов:
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
Вы можете использовать запомненное значение в качестве параметра для других компонуемых элементов или даже в качестве логики в операторах для изменения отображаемых компонуемых элементов. Например, если вы не хотите отображать приветствие, если имя пустое, используйте состояние в операторе if
:
@Composable fun HelloContent() { Column(modifier = Modifier.padding(16.dp)) { var name by remember { mutableStateOf("") } if (name.isNotEmpty()) { Text( text = "Hello, $name!", modifier = Modifier.padding(bottom = 8.dp), style = MaterialTheme.typography.bodyMedium ) } OutlinedTextField( value = name, onValueChange = { name = it }, label = { Text("Name") } ) } }
Хотя remember
помогает сохранять состояние при перекомпоновке, оно не сохраняется при изменении конфигурации. Для этого необходимо использовать rememberSaveable
. rememberSaveable
автоматически сохраняет любое значение, которое можно сохранить в Bundle
. Для других значений можно передать пользовательский объект saver.
Другие поддерживаемые типы состояний
Compose не требует использования MutableState<T>
для хранения состояния; он поддерживает другие наблюдаемые типы. Прежде чем читать другой наблюдаемый тип в Compose, необходимо преобразовать его в State<T>
, чтобы компонуемые объекты могли автоматически перекомпоновываться при изменении состояния.
Составьте пакеты с функциями для создания State<T>
из распространённых наблюдаемых типов, используемых в приложениях Android. Перед использованием этих интеграций добавьте соответствующие артефакты , как описано ниже:
Flow
:collectAsStateWithLifecycle()
collectAsStateWithLifecycle()
собирает значения изFlow
с учётом жизненного цикла, позволяя вашему приложению экономить ресурсы. Он представляет собой последнее значение, отправленное изState
Compose. Используйте этот API в качестве рекомендуемого способа сбора потоков в приложениях Android.В файле
build.gradle
требуется следующая зависимость (версия должна быть 2.6.0-beta01 или новее):
Котлин
dependencies {
...
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.8.7")
}
Круто
dependencies {
...
implementation "androidx.lifecycle:lifecycle-runtime-compose:2.8.7"
}
collectAsState
похож наcollectAsStateWithLifecycle
, поскольку он также собирает значения изFlow
и преобразует их в ComposeState
.Используйте
collectAsState
для платформенно-независимого кода вместоcollectAsStateWithLifecycle
, который доступен только для Android.Для
collectAsState
не требуются дополнительные зависимости, поскольку он доступен вcompose-runtime
.observeAsState()
начинает наблюдение за этимиLiveData
и представляет их значения черезState
.В файле
build.gradle
требуется следующая зависимость :
Котлин
dependencies {
...
implementation("androidx.compose.runtime:runtime-livedata:1.8.1")
}
Круто
dependencies {
...
implementation "androidx.compose.runtime:runtime-livedata:1.8.1"
}
subscribeAsState()
— это функции расширения, которые преобразуют реактивные потоки RxJava2 (например,Single
,Observable
,Completable
) в ComposeState
.В файле
build.gradle
требуется следующая зависимость :
Котлин
dependencies {
...
implementation("androidx.compose.runtime:runtime-rxjava2:1.8.1")
}
Круто
dependencies {
...
implementation "androidx.compose.runtime:runtime-rxjava2:1.8.1"
}
subscribeAsState()
— это функции расширения, которые преобразуют реактивные потоки RxJava3 (например,Single
,Observable
,Completable
) в ComposeState
.В файле
build.gradle
требуется следующая зависимость :
Котлин
dependencies {
...
implementation("androidx.compose.runtime:runtime-rxjava3:1.8.1")
}
Круто
dependencies {
...
implementation "androidx.compose.runtime:runtime-rxjava3:1.8.1"
}
С сохранением состояния против отсутствия состояния
Составной объект, использующий remember
для хранения объекта, создаёт внутреннее состояние, что делает его сохраняющим состояние . HelloContent
— пример сохраняющего состояние составного объекта, поскольку он хранит и изменяет своё name
внутри. Это может быть полезно в ситуациях, когда вызывающему не нужно контролировать состояние, и он может использовать его, не управляя им самостоятельно. Однако составные объекты с внутренним состоянием, как правило, менее пригодны для повторного использования и сложнее в тестировании.
Композируемый объект без состояния — это композируемый объект, не имеющий никакого состояния. Простой способ добиться отсутствия состояния — использовать поднятие состояния .
При разработке повторно используемых составных объектов часто возникает необходимость предоставить как версию с сохранением состояния, так и версию без сохранения состояния одного и того же составного объекта. Версия с сохранением состояния удобна для вызывающих объектов, которым не важно состояние, а версия без сохранения состояния необходима для вызывающих объектов, которым необходимо контролировать или поднимать состояние.
Государственный подъем
Поднятие состояния в Compose — это шаблон передачи состояния вызывающему объекту компонуемого объекта, чтобы сделать его не сохраняющим состояние. Общий шаблон для поднятия состояния в Jetpack Compose заключается в замене переменной состояния двумя параметрами:
-
value: T
: текущее значение для отображения -
onValueChange: (T) -> Unit
: событие, которое запрашивает изменение значения, гдеT
— предлагаемое новое значение
Однако вы не ограничены onValueChange
. Если для компонуемого объекта подходят более конкретные события, их следует определить с помощью лямбда-выражений.
Состояние, поднятое таким образом, имеет некоторые важные свойства:
- Единый источник истины: перемещая состояние вместо его дублирования, мы обеспечиваем наличие единственного источника истины. Это помогает избежать ошибок.
- Инкапсуляция: только компонуемые объекты с сохранением состояния могут изменять своё состояние. Это полностью внутренний процесс.
- Совместное использование: Поднятое состояние может быть общим для нескольких составных объектов. Если вам нужно прочитать
name
в другом составном объекте, поднятие позволит это сделать. - Перехватываемость: вызывающие объекты, не имеющие состояния, могут решить игнорировать или изменять события перед изменением состояния.
- Разделено: состояние для составных объектов без состояния может храниться где угодно. Например, теперь можно переместить
name
воViewModel
.
В этом примере вы извлекаете name
и onValueChange
из HelloContent
и перемещаете их вверх по дереву в составной элемент HelloScreen
, который вызывает HelloContent
.
@Composable fun HelloScreen() { var name by rememberSaveable { mutableStateOf("") } HelloContent(name = name, onNameChange = { name = it }) } @Composable fun HelloContent(name: String, onNameChange: (String) -> Unit) { Column(modifier = Modifier.padding(16.dp)) { Text( text = "Hello, $name", modifier = Modifier.padding(bottom = 8.dp), style = MaterialTheme.typography.bodyMedium ) OutlinedTextField(value = name, onValueChange = onNameChange, label = { Text("Name") }) } }
Вынося состояние за пределы HelloContent
, становится проще анализировать составной элемент, повторно использовать его в различных ситуациях и тестировать. HelloContent
отделен от способа хранения его состояния. Это отделение означает, что при изменении или замене HelloScreen
вам не нужно менять реализацию HelloContent
.

Паттерн, при котором состояние передается вниз, а события — вверх, называется однонаправленным потоком данных . В этом случае состояние передается вниз от HelloScreen
к HelloContent
, а события — вверх от HelloContent
к HelloScreen
. Следуя однонаправленному потоку данных, вы можете отделить компонуемые элементы, отображающие состояние в пользовательском интерфейсе, от частей приложения, которые хранят и изменяют состояние.
Более подробную информацию см. на странице «Где поднять состояние» .
Восстановление состояния в Compose
API rememberSaveable
работает аналогично remember
, поскольку сохраняет состояние при рекомпозиции, а также при повторном создании действий или процессов, используя механизм сохранённого состояния экземпляра. Например, это происходит при повороте экрана.
Способы хранения состояния
Все типы данных, добавляемые в Bundle
, сохраняются автоматически. Если вы хотите сохранить что-то, что нельзя добавить в Bundle
, есть несколько вариантов.
Парцеллизировать
Простейшее решение — добавить к объекту аннотацию @Parcelize
. Объект становится пригодным для пакетирования и может быть упакован. Например, этот код создаёт тип данных City
, пригодный для пакетирования, и сохраняет его в состоянии.
@Parcelize data class City(val name: String, val country: String) : Parcelable @Composable fun CityScreen() { var selectedCity = rememberSaveable { mutableStateOf(City("Madrid", "Spain")) } }
MapSaver
Если по каким-то причинам @Parcelize
не подходит, вы можете использовать mapSaver
для определения собственного правила преобразования объекта в набор значений, которые система может сохранить в Bundle
.
data class City(val name: String, val country: String) val CitySaver = run { val nameKey = "Name" val countryKey = "Country" mapSaver( save = { mapOf(nameKey to it.name, countryKey to it.country) }, restore = { City(it[nameKey] as String, it[countryKey] as String) } ) } @Composable fun CityScreen() { var selectedCity = rememberSaveable(stateSaver = CitySaver) { mutableStateOf(City("Madrid", "Spain")) } }
ListSaver
Чтобы избежать необходимости определять ключи для карты, вы также можете использовать listSaver
и использовать его индексы в качестве ключей:
data class City(val name: String, val country: String) val CitySaver = listSaver<City, Any>( save = { listOf(it.name, it.country) }, restore = { City(it[0] as String, it[1] as String) } ) @Composable fun CityScreen() { var selectedCity = rememberSaveable(stateSaver = CitySaver) { mutableStateOf(City("Madrid", "Spain")) } }
Владельцы штатов в Compose
Простым поднятием состояний можно управлять непосредственно в компонуемых функциях. Однако, если объём отслеживаемых состояний увеличивается, или возникает необходимость в реализации логики в компонуемых функциях, рекомендуется делегировать логику и ответственность за состояние другим классам: держателям состояний .
Дополнительную информацию см. в описании подъема состояния в документации Compose или, в более общем плане, на странице «Хранители состояния» и «Состояние пользовательского интерфейса» в руководстве по архитектуре.
Повторно запоминать вычисления при смене ключей
API remember
часто используется вместе с MutableState
:
var name by remember { mutableStateOf("") }
В данном случае использование функции remember
позволяет значению MutableState
сохраняться при перекомпоновках.
В общем случае remember
принимает в качестве параметра лямбда- calculation
. При первом запуске remember
вызывает лямбда-функцию calculation
и сохраняет её результат. Во время рекомпозиции remember
возвращает последнее сохранённое значение.
Помимо кэширования состояния, вы также можете использовать функцию remember
для сохранения любого объекта или результата операции в композиции, инициализация или вычисление которой требует больших затрат. Возможно, вам не захочется повторять эти вычисления при каждой перекомпозиции. Примером может служить создание объекта ShaderBrush
, что является дорогостоящей операцией:
val brush = remember { ShaderBrush( BitmapShader( ImageBitmap.imageResource(res, avatarRes).asAndroidBitmap(), Shader.TileMode.REPEAT, Shader.TileMode.REPEAT ) ) }
remember
сохраняет значение до тех пор, пока оно не покинет композицию. Однако существует способ сделать кэшированное значение недействительным. API remember
также принимает один или несколько параметров key
( keys
. Если какой-либо из этих ключей изменится, при следующей перекомпозиции функции remember
аннулирует кэш и снова выполнит лямбда-блок вычисления . Этот механизм позволяет контролировать время существования объекта в композиции. Вычисление остаётся действительным до тех пор, пока не изменятся входные данные, а не до тех пор, пока запомненное значение не покинет композицию.
Следующие примеры показывают, как работает этот механизм.
В этом фрагменте кода создаётся ShaderBrush
, который используется в качестве фоновой заливки для составного объекта Box
. remember
сохраняет экземпляр ShaderBrush
, поскольку его повторное создание требует больших затрат, как объяснялось ранее. remember
принимает в качестве параметра key1
avatarRes
— выбранное фоновое изображение. Если avatarRes
изменяется, кисть перекомпоновывается с новым изображением и повторно применяется к Box
. Это может произойти, когда пользователь выбирает другое изображение в качестве фона в палитре.
@Composable private fun BackgroundBanner( @DrawableRes avatarRes: Int, modifier: Modifier = Modifier, res: Resources = LocalContext.current.resources ) { val brush = remember(key1 = avatarRes) { ShaderBrush( BitmapShader( ImageBitmap.imageResource(res, avatarRes).asAndroidBitmap(), Shader.TileMode.REPEAT, Shader.TileMode.REPEAT ) ) } Box( modifier = modifier.background(brush) ) { /* ... */ } }
В следующем фрагменте кода состояние переносится в простой класс-держатель состояния MyAppState
. Он предоставляет функцию rememberMyAppState
для инициализации экземпляра класса с помощью remember
. Предоставление таких функций для создания экземпляра, сохраняющего работоспособность при перекомпоновке, является распространённым шаблоном в Compose. Функция rememberMyAppState
получает windowSizeClass
, который служит key
параметром для remember
. Если этот параметр изменяется, приложению необходимо пересоздать простой класс-держатель состояния с последним значением. Это может произойти, например, если пользователь повернёт устройство.
@Composable private fun rememberMyAppState( windowSizeClass: WindowSizeClass ): MyAppState { return remember(windowSizeClass) { MyAppState(windowSizeClass) } } @Stable class MyAppState( private val windowSizeClass: WindowSizeClass ) { /* ... */ }
Compose использует реализацию equals класса, чтобы определить, изменился ли ключ, и сделать сохраненное значение недействительным.
Сохранение состояния с ключами, не подлежащими повторной композиции
API rememberSaveable
— это оболочка для remember
, которая позволяет хранить данные в Bundle
. Этот API позволяет сохранять состояние не только при перекомпоновке, но и при воссоздании активности, а также при завершении процесса, инициированном системой. rememberSaveable
получает input
параметры с той же целью, что и remember
получает keys
. Кэш становится недействительным при изменении любого из входных данных . При следующей перекомпоновке функции rememberSaveable
повторно выполняет вычисляемый лямбда-блок.
В следующем примере rememberSaveable
сохраняет userTypedQuery
до тех пор, пока не изменится typedQuery
:
var userTypedQuery by rememberSaveable(typedQuery, stateSaver = TextFieldValue.Saver) { mutableStateOf( TextFieldValue(text = typedQuery, selection = TextRange(typedQuery.length)) ) }
Узнать больше
Чтобы узнать больше о состоянии и Jetpack Compose, ознакомьтесь со следующими дополнительными ресурсами.
Образцы
Codelabs
Видео
Блоги
{% дословно %}Рекомендовано для вас
- Примечание: текст ссылки отображается, когда JavaScript отключен.
- Разработка архитектуры пользовательского интерфейса Compose
- Сохранение состояния пользовательского интерфейса в Compose
- Побочные эффекты в Compose