Состояние в приложении — это любое значение, которое может меняться со временем. Это очень широкое определение, охватывающее все: от базы данных 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
сохраняется в композиции во время первоначальной композиции, и сохраненное значение возвращается во время повторной композиции. 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
. Для других значений вы можете передать собственный объект заставки.
Другие поддерживаемые типы состояний
Compose не требует использования MutableState<T>
для хранения состояния; он поддерживает другие наблюдаемые типы. Прежде чем читать другой наблюдаемый тип в Compose, вы должны преобразовать его в State<T>
, чтобы составные объекты могли автоматически перекомпоновываться при изменении состояния.
Compose поставляется с функциями для создания 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.5")
}
классный
dependencies {
...
implementation "androidx.lifecycle:lifecycle-runtime-compose:2.8.5"
}
collectAsState
похож наcollectAsStateWithLifecycle
, поскольку он также собирает значения изFlow
и преобразует их в ComposeState
.Используйте
collectAsState
для кода, не зависящего от платформы, вместоcollectAsStateWithLifecycle
, который предназначен только для Android.Дополнительные зависимости для
collectAsState
не требуются, поскольку они доступны вcompose-runtime
.observeAsState()
начинает наблюдать за этимиLiveData
и представляет их значения черезState
.В файле
build.gradle
требуется следующая зависимость :
Котлин
dependencies {
...
implementation("androidx.compose.runtime:runtime-livedata:1.7.0")
}
классный
dependencies {
...
implementation "androidx.compose.runtime:runtime-livedata:1.7.0"
}
subscribeAsState()
— это функции расширения, которые преобразуют реактивные потоки RxJava2 (например,Single
,Observable
,Completable
) в ComposeState
.В файле
build.gradle
требуется следующая зависимость :
Котлин
dependencies {
...
implementation("androidx.compose.runtime:runtime-rxjava2:1.7.0")
}
классный
dependencies {
...
implementation "androidx.compose.runtime:runtime-rxjava2:1.7.0"
}
subscribeAsState()
— это функции расширения, которые преобразуют реактивные потоки RxJava3 (например,Single
,Observable
,Completable
) в ComposeState
.В файле
build.gradle
требуется следующая зависимость :
Котлин
dependencies {
...
implementation("androidx.compose.runtime:runtime-rxjava3:1.7.0")
}
классный
dependencies {
...
implementation "androidx.compose.runtime:runtime-rxjava3:1.7.0"
}
С сохранением состояния и без гражданства
Компонуемый объект, использующий функцию 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 использует реализацию равенства класса, чтобы определить, изменился ли ключ, и сделать сохраненное значение недействительным.
Сохранение состояния с ключами, не подлежащими рекомпозиции
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, обратитесь к следующим дополнительным ресурсам.
Образцы
Кодлабы
Видео
Блоги
{% дословно %}Рекомендуется для вас
- Примечание: текст ссылки отображается, когда JavaScript отключен.
- Архитектура пользовательского интерфейса Compose
- Сохранение состояния пользовательского интерфейса в Compose
- Побочные эффекты в Compose
Состояние в приложении — это любое значение, которое может меняться со временем. Это очень широкое определение, охватывающее все: от базы данных 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
сохраняется в композиции во время первоначальной композиции, и сохраненное значение возвращается во время повторной композиции. 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
. Для других значений вы можете передать собственный объект заставки.
Другие поддерживаемые типы состояний
Compose не требует использования MutableState<T>
для хранения состояния; он поддерживает другие наблюдаемые типы. Прежде чем читать другой наблюдаемый тип в Compose, вы должны преобразовать его в State<T>
, чтобы составные объекты могли автоматически перекомпоновываться при изменении состояния.
Compose поставляется с функциями для создания 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.5")
}
классный
dependencies {
...
implementation "androidx.lifecycle:lifecycle-runtime-compose:2.8.5"
}
collectAsState
похож наcollectAsStateWithLifecycle
, поскольку он также собирает значения изFlow
и преобразует их в ComposeState
.Используйте
collectAsState
для кода, не зависящего от платформы, вместоcollectAsStateWithLifecycle
, который предназначен только для Android.Дополнительные зависимости для
collectAsState
не требуются, поскольку они доступны вcompose-runtime
.observeAsState()
начинает наблюдать за этимиLiveData
и представляет их значения черезState
.В файле
build.gradle
требуется следующая зависимость :
Котлин
dependencies {
...
implementation("androidx.compose.runtime:runtime-livedata:1.7.0")
}
классный
dependencies {
...
implementation "androidx.compose.runtime:runtime-livedata:1.7.0"
}
subscribeAsState()
— это функции расширения, которые преобразуют реактивные потоки RxJava2 (например,Single
,Observable
,Completable
) в ComposeState
.В файле
build.gradle
требуется следующая зависимость :
Котлин
dependencies {
...
implementation("androidx.compose.runtime:runtime-rxjava2:1.7.0")
}
классный
dependencies {
...
implementation "androidx.compose.runtime:runtime-rxjava2:1.7.0"
}
subscribeAsState()
— это функции расширения, которые преобразуют реактивные потоки RxJava3 (например,Single
,Observable
,Completable
) в ComposeState
.В файле
build.gradle
требуется следующая зависимость :
Котлин
dependencies {
...
implementation("androidx.compose.runtime:runtime-rxjava3:1.7.0")
}
классный
dependencies {
...
implementation "androidx.compose.runtime:runtime-rxjava3:1.7.0"
}
С сохранением состояния и без гражданства
Компонуемый объект, использующий функцию 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 использует реализацию равенства класса, чтобы определить, изменился ли ключ, и сделать сохраненное значение недействительным.
Сохранение состояния с ключами, не подлежащими рекомпозиции
API rememberSaveable
— это оболочка remember
, которая может хранить данные в Bundle
. Этот API позволяет состоянию пережить не только рекомпозицию, но и воссоздание активности и смерть процесса, инициированного системой. rememberSaveable
получает input
параметры с той же целью, что и remember
получает keys
. Кэш становится недействительным при изменении любого из входных данных . В следующий раз, когда функция перекладывается, rememberSaveable
повторно использует расчет Lambda Block.
В следующем примере rememberSaveable
, что он хранит userTypedQuery
пока не изменяется typedQuery
:
var userTypedQuery by rememberSaveable(typedQuery, stateSaver = TextFieldValue.Saver) { mutableStateOf( TextFieldValue(text = typedQuery, selection = TextRange(typedQuery.length)) ) }
Узнать больше
Чтобы узнать больше о штате и Jetpack Compose, обратитесь к следующим дополнительным ресурсам.
Образцы
Кодлабы
Видео
Блоги
{% дословно %}Рекомендуется для вас
- Примечание: текст ссылки отображается, когда JavaScript отключен.
- Архитектура пользовательского интерфейса Compose
- Сохранить состояние пользовательского интерфейса в Compose
- Побочные эффекты в Compose
Государство в приложении - это любое значение, которое может измениться со временем. Это очень широкое определение и охватывает все, от базы данных комнаты до переменной в классе.
Все приложения Android отображают состояние пользователю. Несколько примеров состояния в приложениях Android:
- Закусочная, которая показывает, когда сетевое соединение не может быть установлено.
- Пост в блоге и связанные с ними комментарии.
- Анимации с пуговицами на кнопках, которые играют, когда пользователь нажимает на них.
- Наклейки, которые пользователь может рисовать поверх изображения.
JetPack Compose помогает вам быть четко подходит к тому, где и как вы храните и используете состояние в приложении Android. Это руководство фокусируется на связи между состоянием и композициями, а также на API, которые JetPack составляет предложения для более легкой работы с состоянием.
Состояние и композиция
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
хранится в композиции во время начального состава, и хранимое значение возвращается во время переоборудования. 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>
для удержания состояния; Он поддерживает другие наблюдаемые типы. Перед чтением другого наблюдаемого типа в композите вы должны преобразовать его в State<T>
, чтобы композиции могли автоматически пересказывать при изменении состояния.
Составьте корабли с функциями для создания State<T>
из общих наблюдаемых типов, используемых в приложениях Android. Перед использованием этих интеграций добавьте соответствующий артефакт (ы), как указано ниже:
Flow
:collectAsStateWithLifecycle()
collectAsStateWithLifecycle()
собирает значения изFlow
в жизненном цикле, позволяя вашему приложению сохранять ресурсы приложений. Он представляет собой новейшее излучаемое значение изState
композиции. Используйте этот API в качестве рекомендуемого способа сбора потоков на приложениях Android.Следующая зависимость требуется в файле
build.gradle
(она должна быть 2.6.0-beta01 или новее):
Котлин
dependencies {
...
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.8.5")
}
классный
dependencies {
...
implementation "androidx.lifecycle:lifecycle-runtime-compose:2.8.5"
}
collectAsState
аналогичнаcollectAsStateWithLifecycle
, потому что он также собирает значения изFlow
и превращает их вState
сочинения.Используйте
collectAsState
для платформы-агрессического кода вместоcollectAsStateWithLifecycle
, который является только для Android.Дополнительные зависимости не требуются для
collectAsState
, поскольку она доступна вcompose-runtime
.observeAsState()
начинает наблюдать за этойLiveData
и представляет его значения черезState
.В файле
build.gradle
требуется следующая зависимость :
Котлин
dependencies {
...
implementation("androidx.compose.runtime:runtime-livedata:1.7.0")
}
классный
dependencies {
...
implementation "androidx.compose.runtime:runtime-livedata:1.7.0"
}
subscribeAsState()
- это функции разгибания, которые превращают реактивные потоки Rxjava2 (например,Single
,Observable
,Completable
) вState
сочинения.В файле
build.gradle
требуется следующая зависимость :
Котлин
dependencies {
...
implementation("androidx.compose.runtime:runtime-rxjava2:1.7.0")
}
классный
dependencies {
...
implementation "androidx.compose.runtime:runtime-rxjava2:1.7.0"
}
subscribeAsState()
- это функции расширения, которые превращают реактивные потоки Rxjava3 (например,Single
,Observable
,Completable
) вState
сочинения.В файле
build.gradle
требуется следующая зависимость :
Котлин
dependencies {
...
implementation("androidx.compose.runtime:runtime-rxjava3:1.7.0")
}
классный
dependencies {
...
implementation "androidx.compose.runtime:runtime-rxjava3:1.7.0"
}
Государственные и без гражданства
Компонируемый, который использует remember
хранить объект, создает внутреннее состояние, делая композицию Stateful . HelloContent
является примером состояния композиции, потому что он удерживает и изменяет свое name
штата внутренне. Это может быть полезно в ситуациях, когда вызывающему абоненту не нужно контролировать состояние и может использовать его без необходимости самостоятельно управлять государством. Тем не менее, композиции с внутренним состоянием, как правило, менее повторно используются и труднее тестировать.
Компонируемый состав без гражданства - это композитный, который не содержит никакого состояния. Простой способ достичь бездействия - это использование государственного подъема .
Когда вы разрабатываете многоразовые композиции, вы часто хотите разоблачить как государственную, так и без сохранности версии одного и того же композиции. Стативная версия удобна для абонентов, которым не заботятся о состоянии, а версия без сохранения состояния необходима для абонентов, которым необходимо контролировать или поднимать государство.
Государственный подъем
Государственное подъем в Compose - это схема перемещения состояния к абоненту композиции, чтобы сделать композиционный без вести. Общий шаблон для подъема штата в JetPack Compose заключается в замене переменной состояния двумя параметрами:
-
value: T
: текущее значение для отображения -
onValueChange: (T) -> Unit
: событие, которое запрашивает значение изменения, гдеT
-предложенное новое значение
Тем не менее, вы не ограничены onValueChange
. Если для композиции подходят более конкретные события, вы должны определить их с помощью Lambdas.
Утверждение, которое поднимается таким образом, обладает некоторыми важными свойствами:
- Единственный источник истины: движущееся состояние вместо дублирования его, мы гарантируем, что есть только один источник истины. Это помогает избежать ошибок.
- Инкапсулированные: только состоятельные композиции могут изменить свое состояние. Это полностью внутреннее.
- Общее: состояние подъема может быть использовано с несколькими композиционными продуктами. Если вы хотите прочитать
name
в другом композитном, подъемник позволил бы вам сделать это. - Interceptable: вызывающие абоненты в композиции без состояния могут решить игнорировать или изменить события, прежде чем изменить состояние.
- Разрешение: состояние для без сохранения состояния может храниться где угодно. Например, теперь можно переместить
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
. Следуя однонаправленному потоку данных, вы можете отделять композиции, которые отображают состояние в пользовательском интерфейсе из частей вашего приложения, которые хранят и изменяют состояние.
Посмотрите , где поднять страницу состояния, чтобы узнать больше.
Восстановление состояния в составлении
rememberSaveable
API, который ведет себя так же, как 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")) } }
Картинка
Если по какой -то причине @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
и использовать его индексы в качестве ключей:
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 или, в целом, государственные держатели и страницу государственного пользовательского интерфейса в Руководстве по архитектуре, чтобы узнать больше.
Retrigger запомните расчеты при изменении ключей
API remember
часто используется вместе с MutableState
:
var name by remember { mutableStateOf("") }
Здесь использование функции remember
заставляет значение MutableState
выживание переоборудования.
В целом, remember
что calculation
параметр Lambda. Когда remember
первым запуска, он вызывает calculation
Lambda и хранит свой результат. Во время переоборудования 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
о недействительном кеше и снова выполняет блок расчета Lambda . Этот механизм дает вам контроль над временем жизни объекта в композиции. Расчет остается действительным до тех пор, пока входы не изменятся, а не до тех пор, пока запомненное значение не покинет композицию.
Следующие примеры показывают, как работает этот механизм.
В этом фрагменте создается и используется ShaderBrush
в качестве фоновой краски композитной Box
. remember
что храните экземпляр ShaderBrush
, потому что воссоздать его дорого, как объяснялось ранее. remember
что Takes avatarRes
как параметр key1
, который является выбранным фоновым изображением. Если 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
. Раскрытие таких функций для создания экземпляра, которое выживает, переоборудования является распространенной схемой в составе. Функция 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
API - это обертка, которая remember
, что может хранить данные в Bundle
. Этот API позволяет утверждать, что выжить не только на переоборудование, но и на развлечения и инициированную системой смерть процесса. rememberSaveable
получает input
параметры для той же цели, которая remember
получает keys
. Кэш недействителен при изменении какого -либо из входов . В следующий раз, когда функция перекладывается, rememberSaveable
повторно использует расчет Lambda Block.
В следующем примере rememberSaveable
, что он хранит userTypedQuery
пока не изменяется typedQuery
:
var userTypedQuery by rememberSaveable(typedQuery, stateSaver = TextFieldValue.Saver) { mutableStateOf( TextFieldValue(text = typedQuery, selection = TextRange(typedQuery.length)) ) }
Узнать больше
Чтобы узнать больше о штате и Jetpack Compose, обратитесь к следующим дополнительным ресурсам.
Образцы
Кодлабы
Видео
Блоги
{% дословно %}Рекомендуется для вас
- Примечание: текст ссылки отображается, когда JavaScript отключен.
- Архитектура пользовательского интерфейса Compose
- Сохранить состояние пользовательского интерфейса в Compose
- Побочные эффекты в Compose
Государство в приложении - это любое значение, которое может измениться со временем. Это очень широкое определение и охватывает все, от базы данных комнаты до переменной в классе.
Все приложения Android отображают состояние пользователю. Несколько примеров состояния в приложениях Android:
- Закусочная, которая показывает, когда сетевое соединение не может быть установлено.
- Пост в блоге и связанные с ними комментарии.
- Анимации с пуговицами на кнопках, которые играют, когда пользователь нажимает на них.
- Наклейки, которые пользователь может рисовать поверх изображения.
JetPack Compose помогает вам быть четко подходит к тому, где и как вы храните и используете состояние в приложении Android. Это руководство фокусируется на связи между состоянием и композициями, а также на API, которые JetPack составляет предложения для более легкой работы с состоянием.
Состояние и композиция
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
хранится в композиции во время начального состава, и хранимое значение возвращается во время переоборудования. 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>
для удержания состояния; Он поддерживает другие наблюдаемые типы. Перед чтением другого наблюдаемого типа в композите вы должны преобразовать его в State<T>
, чтобы композиции могли автоматически пересказывать при изменении состояния.
Составьте корабли с функциями для создания State<T>
из общих наблюдаемых типов, используемых в приложениях Android. Перед использованием этих интеграций добавьте соответствующий артефакт (ы), как указано ниже:
Flow
:collectAsStateWithLifecycle()
collectAsStateWithLifecycle()
собирает значения изFlow
в жизненном цикле, позволяя вашему приложению сохранять ресурсы приложений. Он представляет собой новейшее излучаемое значение изState
композиции. Используйте этот API в качестве рекомендуемого способа сбора потоков на приложениях Android.Следующая зависимость требуется в файле
build.gradle
(она должна быть 2.6.0-beta01 или новее):
Котлин
dependencies {
...
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.8.5")
}
классный
dependencies {
...
implementation "androidx.lifecycle:lifecycle-runtime-compose:2.8.5"
}
collectAsState
аналогичнаcollectAsStateWithLifecycle
, потому что он также собирает значения изFlow
и превращает их вState
сочинения.Используйте
collectAsState
для платформы-агрессического кода вместоcollectAsStateWithLifecycle
, который является только для Android.Дополнительные зависимости не требуются для
collectAsState
, поскольку она доступна вcompose-runtime
.observeAsState()
начинает наблюдать за этойLiveData
и представляет его значения черезState
.В файле
build.gradle
требуется следующая зависимость :
Котлин
dependencies {
...
implementation("androidx.compose.runtime:runtime-livedata:1.7.0")
}
классный
dependencies {
...
implementation "androidx.compose.runtime:runtime-livedata:1.7.0"
}
subscribeAsState()
- это функции разгибания, которые превращают реактивные потоки Rxjava2 (например,Single
,Observable
,Completable
) вState
сочинения.В файле
build.gradle
требуется следующая зависимость :
Котлин
dependencies {
...
implementation("androidx.compose.runtime:runtime-rxjava2:1.7.0")
}
классный
dependencies {
...
implementation "androidx.compose.runtime:runtime-rxjava2:1.7.0"
}
subscribeAsState()
- это функции расширения, которые превращают реактивные потоки Rxjava3 (например,Single
,Observable
,Completable
) вState
сочинения.В файле
build.gradle
требуется следующая зависимость :
Котлин
dependencies {
...
implementation("androidx.compose.runtime:runtime-rxjava3:1.7.0")
}
классный
dependencies {
...
implementation "androidx.compose.runtime:runtime-rxjava3:1.7.0"
}
Государственные и без гражданства
Компонируемый, который использует remember
хранить объект, создает внутреннее состояние, делая композицию Stateful . HelloContent
является примером состояния композиции, потому что он удерживает и изменяет свое name
штата внутренне. Это может быть полезно в ситуациях, когда вызывающему абоненту не нужно контролировать состояние и может использовать его без необходимости самостоятельно управлять государством. Тем не менее, композиции с внутренним состоянием, как правило, менее повторно используются и труднее тестировать.
Компонируемый состав без гражданства - это композитный, который не содержит никакого состояния. Простой способ достичь бездействия - это использование государственного подъема .
Когда вы разрабатываете многоразовые композиции, вы часто хотите разоблачить как государственную, так и без сохранности версии одного и того же композиции. Стативная версия удобна для абонентов, которым не заботятся о состоянии, а версия без сохранения состояния необходима для абонентов, которым необходимо контролировать или поднимать государство.
Государственный подъем
Государственное подъем в Compose - это схема перемещения состояния к абоненту композиции, чтобы сделать композиционный без вести. Общий шаблон для подъема штата в JetPack Compose заключается в замене переменной состояния двумя параметрами:
-
value: T
: текущее значение для отображения -
onValueChange: (T) -> Unit
: событие, которое запрашивает значение изменения, гдеT
-предложенное новое значение
Тем не менее, вы не ограничены onValueChange
. Если для композиции подходят более конкретные события, вы должны определить их с помощью Lambdas.
Утверждение, которое поднимается таким образом, обладает некоторыми важными свойствами:
- Единственный источник истины: движущееся состояние вместо дублирования его, мы гарантируем, что есть только один источник истины. Это помогает избежать ошибок.
- Инкапсулированные: только состоятельные композиции могут изменить свое состояние. Это полностью внутреннее.
- Общее: состояние подъема может быть использовано с несколькими композиционными продуктами. Если вы хотите прочитать
name
в другом композитном, подъемник позволил бы вам сделать это. - Interceptable: вызывающие абоненты в композиции без состояния могут решить игнорировать или изменить события, прежде чем изменить состояние.
- Разрешение: состояние для без сохранения состояния может храниться где угодно. Например, теперь можно переместить
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
. Следуя однонаправленному потоку данных, вы можете отделять композиции, которые отображают состояние в пользовательском интерфейсе из частей вашего приложения, которые хранят и изменяют состояние.
Посмотрите , где поднять страницу состояния, чтобы узнать больше.
Восстановление состояния в составлении
rememberSaveable
API, который ведет себя так же, как 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")) } }
Картинка
Если по какой -то причине @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
и использовать его индексы в качестве ключей:
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 или, в целом, государственные держатели и страницу государственного пользовательского интерфейса в Руководстве по архитектуре, чтобы узнать больше.
Retrigger запомните расчеты при изменении ключей
API remember
часто используется вместе с MutableState
:
var name by remember { mutableStateOf("") }
Здесь использование функции remember
заставляет значение MutableState
выживание переоборудования.
В целом, remember
что calculation
параметр Lambda. Когда remember
первым запуска, он вызывает calculation
Lambda и хранит свой результат. Во время переоборудования 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
о недействительном кеше и снова выполняет блок расчета Lambda . Этот механизм дает вам контроль над временем жизни объекта в композиции. Расчет остается действительным до тех пор, пока входы не изменятся, а не до тех пор, пока запомненное значение не покинет композицию.
Следующие примеры показывают, как работает этот механизм.
В этом фрагменте создается и используется ShaderBrush
в качестве фоновой краски композитной Box
. remember
что храните экземпляр ShaderBrush
, потому что воссоздать его дорого, как объяснялось ранее. remember
что Takes avatarRes
как параметр key1
, который является выбранным фоновым изображением. Если 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
. Раскрытие таких функций для создания экземпляра, которое выживает, переоборудования является распространенной схемой в составе. Функция 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 , чтобы решить, изменился ли ключ, и недействителен хранимое значение.
Store state with keys beyond recomposition
The rememberSaveable
API is a wrapper around remember
that can store data in a Bundle
. This API allows state to survive not only recomposition, but also activity recreation and system-initiated process death. rememberSaveable
receives input
parameters for the same purpose that remember
receives keys
. The cache is invalidated when any of the inputs change . The next time the function recomposes, rememberSaveable
re-executes the calculation lambda block.
In the following example, rememberSaveable
stores userTypedQuery
until typedQuery
changes:
var userTypedQuery by rememberSaveable(typedQuery, stateSaver = TextFieldValue.Saver) { mutableStateOf( TextFieldValue(text = typedQuery, selection = TextRange(typedQuery.length)) ) }
Узнать больше
To learn more about state and Jetpack Compose, consult the following additional resources.
Образцы
Кодлабы
Видео
Блоги
{% дословно %}Рекомендуется для вас
- Примечание: текст ссылки отображается, когда JavaScript отключен.
- Архитектура пользовательского интерфейса Compose
- Save UI state in Compose
- Побочные эффекты в Compose