State и Jetpack 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"
}
  • Flow : collectAsState()

    collectAsState похож на collectAsStateWithLifecycle , поскольку он также собирает значения из Flow и преобразует их в Compose State .

    Используйте collectAsState для кода, не зависящего от платформы, вместо collectAsStateWithLifecycle , который предназначен только для Android.

    Дополнительные зависимости для collectAsState не требуются, поскольку они доступны в compose-runtime .

  • LiveData : observeAsState()

    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"
}

Котлин

dependencies {
      ...
      implementation("androidx.compose.runtime:runtime-rxjava2:1.7.0")
}

классный

dependencies {
      ...
      implementation "androidx.compose.runtime:runtime-rxjava2:1.7.0"
}

Котлин

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, обратитесь к следующим дополнительным ресурсам.

Образцы

Кодлабы

Видео

Блоги

{% дословно %} {% дословно %} {% дословно %} {% endverbatim %} ,

Состояние в приложении — это любое значение, которое может меняться со временем. Это очень широкое определение, охватывающее все: от базы данных 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"
}
  • Flow : collectAsState()

    collectAsState похож на collectAsStateWithLifecycle , поскольку он также собирает значения из Flow и преобразует их в Compose State .

    Используйте collectAsState для кода, не зависящего от платформы, вместо collectAsStateWithLifecycle , который предназначен только для Android.

    Дополнительные зависимости для collectAsState не требуются, поскольку они доступны в compose-runtime .

  • LiveData : observeAsState()

    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"
}

Котлин

dependencies {
      ...
      implementation("androidx.compose.runtime:runtime-rxjava2:1.7.0")
}

классный

dependencies {
      ...
      implementation "androidx.compose.runtime:runtime-rxjava2:1.7.0"
}

Котлин

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, обратитесь к следующим дополнительным ресурсам.

Образцы

Кодлабы

Видео

Блоги

{% дословно %} {% дословно %} {% дословно %} {% endverbatim %} ,

Государство в приложении - это любое значение, которое может измениться со временем. Это очень широкое определение и охватывает все, от базы данных комнаты до переменной в классе.

Все приложения 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"
}
  • Flow : collectAsState()

    collectAsState аналогична collectAsStateWithLifecycle , потому что он также собирает значения из Flow и превращает их в State сочинения.

    Используйте collectAsState для платформы-агрессического кода вместо collectAsStateWithLifecycle , который является только для Android.

    Дополнительные зависимости не требуются для collectAsState , поскольку она доступна в compose-runtime .

  • LiveData : observeAsState()

    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"
}

Котлин

dependencies {
      ...
      implementation("androidx.compose.runtime:runtime-rxjava2:1.7.0")
}

классный

dependencies {
      ...
      implementation "androidx.compose.runtime:runtime-rxjava2:1.7.0"
}

Котлин

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, обратитесь к следующим дополнительным ресурсам.

Образцы

Кодлабы

Видео

Блоги

{% дословно %} {% дословно %} {% дословно %} {% endverbatim %} ,

Государство в приложении - это любое значение, которое может измениться со временем. Это очень широкое определение и охватывает все, от базы данных комнаты до переменной в классе.

Все приложения 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"
}
  • Flow : collectAsState()

    collectAsState аналогична collectAsStateWithLifecycle , потому что он также собирает значения из Flow и превращает их в State сочинения.

    Используйте collectAsState для платформы-агрессического кода вместо collectAsStateWithLifecycle , который является только для Android.

    Дополнительные зависимости не требуются для collectAsState , поскольку она доступна в compose-runtime .

  • LiveData : observeAsState()

    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"
}

Котлин

dependencies {
      ...
      implementation("androidx.compose.runtime:runtime-rxjava2:1.7.0")
}

классный

dependencies {
      ...
      implementation "androidx.compose.runtime:runtime-rxjava2:1.7.0"
}

Котлин

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.

Образцы

Кодлабы

Видео

Блоги

{% дословно %} {% дословно %} {% дословно %} {% дословно %}