Прямые ссылки на публичные уроки для быстрого старта и стабильной индексации lesson-страниц.
В прошлом уроке мы разобрались с хранением состояния и рекомпозицией. Теперь можем работать с composable функциями, которые требуют объявления стейта. В качестве демонстрации реализуем ввод электронной почты в текстовое поле.
Быстрая подготовка: в стандартной обёртке вызвана функция StudyAppHeader(), тело которой находится в другом файле. Это нормально. Дополнительно добавим параметры заголовка и подзаголовка в функцию, чтобы переиспользовать её с любыми строками, сохраняя единый стиль. Благодаря передаче данных через параметры можно выносить бизнес-логику в отдельные функции или во ViewModel.
Также внизу уже есть заготовка кнопки для следующего урока. Ничего нового: пустой onClick, скруглённые углы, высота, горизонтальные отступы и стиль текста.
@Composable @Preview fun PrimaryButton() { Button( shape = RoundedCornerShape(13.dp), onClick = {}, modifier = Modifier .height(56.dp) .padding(40.dp, 0.dp) .fillMaxWidth() ) { Text( "Зарегистрироваться", style = MaterialTheme.typography.labelMedium ) } }
Текстовое поле и текст результатов регистрации разместим в отдельной функции.
В XML верстке использовался EditText. В Jetpack Compose его аналог — TextField. При вводе TextField в IDE видны и другие варианты:
Выбираем OutlinedTextField — он больше подходит по стилю. Функция имеет множество параметров, первые два обязательные:
value — отображает текущее содержимое поля. Можно вписать строку, и она отобразится как будто введена заранее.onValueChange — лямбда, которая вызывается при каждом изменении текста (ввод или удаление символа), аналогично onCheckedChange у чекбокса.shape — скопируем со стиля кнопок для единообразия.textStyle — добавим из переопределённых стилей.@Composable @Preview(showBackground = true) fun CheckEmailFields() { OutlinedTextField( value = "42 42 42", onValueChange = {}, shape = RoundedCornerShape(13.dp), textStyle = MaterialTheme.typography.headlineMedium, ) }
Строка из value отображается в поле, курсор активируется, но ничего напечатать или стереть нельзя. Причина та же, что с чекбоксом: Compose не знает, когда вызывать рекомпозицию. Воспользуемся MutableState в связке с remember, делегат by сделает код лаконичнее. В лямбде onValueChange присваиваем новое значение стейту.
@Composable @Preview(showBackground = true) fun CheckEmailFields() { var textState by remember { mutableStateOf("") } OutlinedTextField( value = textState, onValueChange = { textState = it }, shape = RoundedCornerShape(13.dp), textStyle = MaterialTheme.typography.headlineMedium, ) }
Стейт подхватывает изменения при вводе каждого символа и вызывает рекомпозицию. Уберём значение по умолчанию и добавим плейсхолдер через соответствующий параметр OutlinedTextField. Он принимает composable лямбду, поэтому строка устанавливается функцией Text с нужным оформлением.
По умолчанию при нажатии Enter текстовое поле увеличивается. Чтобы отключить это поведение, добавим параметр singleLine = true (по умолчанию false).
Параметр label задаёт анимированную подпись: при активации поля она смещается вверх к рамке. Текст внутри задаётся функцией Text.
Параметры для иконок внутри поля:
leadingIcon — иконка в начале поля,trailingIcon — иконка в конце.Оба принимают composable функцию. Разместим там IconButton с обязательным параметром onClick. Внутри лямбды IconButton вызываем Icon с иконкой из стандартного набора Material:
trailingIcon = { IconButton( onClick = { textState = "" } ) { Icon( imageVector = Icons.Filled.Clear, contentDescription = "Иконка очистки поля" ) } }
В onClick достаточно присвоить стейту пустую строку — она сразу отрисуется в поле.
Добавим валидацию введённого адреса. Параметр isError при значении true переводит поле в состояние ошибки — рамка окрашивается в красный.
Создадим отдельный стейт для хранения текста ошибки:
var errorState by remember { mutableStateOf("") }
В стейте можно хранить что угодно: Boolean-флаг, код ошибки или целый объект. Здесь выбрана строка — она же будет отображаться в label.
Валидацию проводим в onValueChange после каждого введённого символа. textState не трогаем — текст должен отрисовываться как обычно. errorState присваиваем результат проверки:
errorState = if (EMAIL_ADDRESS.matcher(it).matches()) "" else "Некорректный email"
EMAIL_ADDRESS — стандартное регулярное выражение для проверки формата почты. Если формат корректен — пустая строка (нет ошибки), иначе — сообщение об ошибке.
Убираем хардкод из isError:
isError = errorState.isNotEmpty()
Выводим текст ошибки в label: пока ошибка активна показываем её, иначе — стандартный лейбл:
label = { Text( text = if (errorState.isEmpty()) "Электропочта" else errorState, style = MaterialTheme.typography.headlineSmall, ) },
Последний штрих — очищать errorState при клике на иконку очистки, иначе ошибка остаётся даже после сброса текста:
onClick = { textState = "" errorState = "" }
На следующем уроке продолжим развивать экран регистрации и рассмотрим, как стейты работают не только в рамках одной функции, но и на уровне всего экрана.