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

Следование этому шаблону при использовании Jetpack Compose дает ряд преимуществ:
- Тестируемость : Разделение состояния от отображаемого пользовательского интерфейса упрощает тестирование обоих компонентов по отдельности.
- Инкапсуляция состояния : Поскольку состояние может быть обновлено только в одном месте, и существует только один источник достоверной информации о состоянии составного объекта, вероятность возникновения ошибок из-за несогласованности состояний значительно снижается.
- Согласованность пользовательского интерфейса : все обновления состояния немедленно отражаются в пользовательском интерфейсе благодаря использованию наблюдаемых источников состояния, таких как
StateFlowилиLiveData.
Однонаправленный поток данных в Jetpack Compose
Композируемые объекты работают на основе состояния и событий. Например, 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 , то всякий раз, когда в Header(news) передается новый экземпляр News , компонуемый объект будет пересобран, даже если title и subtitle не изменились.
Тщательно обдумайте количество передаваемых параметров. Функция со слишком большим количеством параметров ухудшает её эргономику, поэтому в данном случае предпочтительнее сгруппировать их в класс.
События в композиции
Каждый ввод данных в ваше приложение должен быть представлен как событие: касания, изменения текста и даже таймеры или другие обновления. По мере того, как эти события изменяют состояние вашего пользовательского интерфейса, 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, states и events: пример
Используя ViewModel и mutableStateOf , вы также можете обеспечить однонаправленный поток данных в вашем приложении, если выполняется одно из следующих условий:
- Состояние вашего пользовательского интерфейса предоставляется с помощью наблюдаемых объектов состояния, таких как
StateFlowилиLiveData. -
ViewModelобрабатывает события, поступающие из пользовательского интерфейса или других слоев вашего приложения, и обновляет содержимое stateholder в зависимости от этих событий.
Например, при реализации экрана входа в систему, нажатие на кнопку «Войти» должно приводить к отображению индикатора выполнения и сообщения о сетевом запросе. Если вход в систему прошел успешно, приложение переходит на другой экран; в случае ошибки приложение отображает 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 and Jetpack Compose
- Сохранение состояния пользовательского интерфейса в Compose
- Обработка пользовательского ввода