В Compose UI неизменяем — нет способа обновить его после того, как он был отрисован. Вы можете контролировать состояние своего UI. Каждый раз, когда состояние UI изменяется, Compose воссоздает измененные части дерева UI . Компонуемые элементы могут принимать состояние и предоставлять события — например, TextField
принимает значение и предоставляет обратный вызов onValueChange
, который запрашивает обработчик обратного вызова для изменения значения.
var name by remember { mutableStateOf("") } OutlinedTextField( value = name, onValueChange = { name = it }, label = { Text("Name") } )
Поскольку компонуемые объекты принимают состояние и выставляют события, шаблон однонаправленного потока данных хорошо подходит для Jetpack Compose. В этом руководстве основное внимание уделяется тому, как реализовать шаблон однонаправленного потока данных в Compose, как реализовать события и держатели состояний, а также как работать с ViewModels в Compose.
Однонаправленный поток данных
Однонаправленный поток данных (UDF) — это шаблон проектирования, в котором состояние течет вниз, а события — вверх. Следуя однонаправленному потоку данных, вы можете отделить компонуемые элементы, которые отображают состояние в пользовательском интерфейсе, от частей вашего приложения, которые хранят и изменяют состояние.
Цикл обновления пользовательского интерфейса для приложения, использующего однонаправленный поток данных, выглядит следующим образом:
- Событие : часть пользовательского интерфейса генерирует событие и передает его наверх, например, нажатие кнопки передается в ViewModel для обработки; или событие передается из других слоев вашего приложения, например, указание на то, что сеанс пользователя истек.
- Обновление состояния : обработчик событий может изменить состояние.
- Отображение состояния : держатель состояния передает состояние, и пользовательский интерфейс отображает его.

Следование этой схеме при использовании Jetpack Compose дает ряд преимуществ:
- Тестируемость : отделение состояния от пользовательского интерфейса, который его отображает, упрощает тестирование обоих по отдельности.
- Инкапсуляция состояний : поскольку состояние может быть обновлено только в одном месте и существует только один источник истины для состояния составного объекта, вероятность возникновения ошибок из-за несогласованных состояний снижается.
- Согласованность пользовательского интерфейса : все обновления состояний немедленно отражаются в пользовательском интерфейсе с помощью наблюдаемых держателей состояний, таких как
StateFlow
илиLiveData
.
Однонаправленный поток данных в Jetpack Compose
Composables работают на основе состояния и событий. Например, TextField
обновляется только тогда, когда обновляется его параметр value
и он выставляет обратный вызов onValueChange
— событие, которое запрашивает изменение значения на новое. Compose определяет объект State
как держатель значения, а изменения значения состояния запускают перекомпозицию. Вы можете удерживать состояние в remember { mutableStateOf(value) }
или rememberSaveable { mutableStateOf(value)
в зависимости от того, как долго вам нужно помнить значение.
Тип значения компонуемого TextField
— String
, поэтому он может поступать откуда угодно — из жестко закодированного значения, из ViewModel или передаваться из родительского компонуемого. Вам не нужно хранить его в объекте State
, но вам нужно обновить значение при вызове onValueChange
.
Определить компонуемые параметры
При определении параметров состояния компонуемого объекта следует иметь в виду следующие вопросы:
- Насколько многоразовым или гибким является компонуемый материал?
- Как параметры состояния влияют на производительность этого компонуемого объекта?
Чтобы поощрять разделение и повторное использование, каждый компонуемый элемент должен содержать как можно меньше информации. Например, при создании компонуемого элемента для хранения заголовка новостной статьи предпочитайте передавать только ту информацию, которую необходимо отобразить, а не всю новостную статью:
@Composable fun Header(title: String, subtitle: String) { // Recomposes when title or subtitle have changed. } @Composable fun Header(news: News) { // Recomposes when a new instance of News is passed in. }
Иногда использование отдельных параметров также повышает производительность — например, если News
содержит больше информации, чем просто title
и subtitle
, то всякий раз, когда новый экземпляр News
передается в Header(news)
, составной объект будет перекомпоновываться, даже если title
и subtitle
не изменились.
Тщательно продумайте количество передаваемых параметров. Наличие функции со слишком большим количеством параметров снижает эргономичность функции, поэтому в этом случае предпочтительнее сгруппировать их в класс.
События в Compose
Каждый ввод в ваше приложение должен быть представлен как событие: нажатия, изменения текста и даже таймеры или другие обновления. Поскольку эти события изменяют состояние вашего пользовательского интерфейса, ViewModel
должен быть тем, кто будет обрабатывать их и обновлять состояние пользовательского интерфейса.
Уровень пользовательского интерфейса никогда не должен изменять состояние вне обработчика событий, поскольку это может привести к несоответствиям и ошибкам в вашем приложении.
Предпочитайте передачу неизменяемых значений для лямбд-выражений состояния и обработчика событий. Этот подход имеет следующие преимущества:
- Вы улучшаете возможность повторного использования.
- Вы гарантируете, что ваш пользовательский интерфейс не изменяет значение состояния напрямую.
- Вы избегаете проблем с параллелизмом, поскольку гарантируете, что состояние не будет изменено из другого потока.
- Часто вы снижаете сложность кода.
Например, компонуемый объект, принимающий String
и лямбду в качестве параметров, может быть вызван из многих контекстов и может многократно использоваться. Предположим, что верхняя панель приложения в вашем приложении всегда отображает текст и имеет кнопку «Назад». Вы можете определить более общий компонуемый объект MyAppTopAppBar
, который получает текст и дескриптор кнопки «Назад» в качестве параметров:
@Composable fun MyAppTopAppBar(topAppBarText: String, onBackPressed: () -> Unit) { TopAppBar( title = { Text( text = topAppBarText, textAlign = TextAlign.Center, modifier = Modifier .fillMaxSize() .wrapContentSize(Alignment.Center) ) }, navigationIcon = { IconButton(onClick = onBackPressed) { Icon( Icons.AutoMirrored.Filled.ArrowBack, contentDescription = localizedString ) } }, // ... ) }
ViewModels, состояния и события: пример
Используя ViewModel
и mutableStateOf
, вы также можете реализовать однонаправленный поток данных в своем приложении, если выполняется одно из следующих условий:
- Состояние вашего пользовательского интерфейса отображается через наблюдаемые держатели состояний, такие как
StateFlow
илиLiveData
. -
ViewModel
обрабатывает события, поступающие из пользовательского интерфейса или других слоев вашего приложения, и обновляет держатель состояния на основе событий.
Например, при реализации экрана входа в систему нажатие на кнопку « Войти» должно заставить ваше приложение отобразить индикатор выполнения и сетевой вызов. Если вход в систему прошел успешно, то ваше приложение переходит на другой экран; в случае ошибки приложение показывает Snackbar. Вот как вы можете смоделировать состояние экрана и событие:
Экран имеет четыре состояния:
- Вышел из системы : пользователь еще не вошел в систему.
- В процессе : когда ваше приложение в данный момент пытается авторизовать пользователя, выполняя сетевой вызов.
- Ошибка : произошла ошибка при входе в систему.
- Выполнен вход : когда пользователь выполнил вход в систему.
Вы можете моделировать эти состояния как запечатанный класс. ViewModel
представляет состояние как State
, устанавливает начальное состояние и обновляет состояние по мере необходимости. ViewModel
также обрабатывает событие входа, предоставляя метод onSignIn()
.
class MyViewModel : ViewModel() { private val _uiState = mutableStateOf<UiState>(UiState.SignedOut) val uiState: State<UiState> get() = _uiState // ... }
В дополнение к API mutableStateOf
Compose предоставляет расширения для LiveData
, Flow
и Observable
для регистрации в качестве прослушивателя и представления значения в виде состояния.
class MyViewModel : ViewModel() { private val _uiState = MutableLiveData<UiState>(UiState.SignedOut) val uiState: LiveData<UiState> get() = _uiState // ... } @Composable fun MyComposable(viewModel: MyViewModel) { val uiState = viewModel.uiState.observeAsState() // ... }
Узнать больше
Чтобы узнать больше об архитектуре Jetpack Compose, обратитесь к следующим ресурсам:
Образцы
Рекомендовано для вас
- Примечание: текст ссылки отображается, когда JavaScript отключен.
- State и Jetpack Compose
- Сохранение состояния пользовательского интерфейса в Compose
- Обработка пользовательского ввода