Урок 9: TextField /OutlinedTextField, Валидация почты, ErrorState
Оглавление:
Введение
В прошлом уроке мы разобрались в критически важной теме, связанной с хранением состояния и рекомпозицией. Теперь мы можем работать с большим количеством 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 — аналогичный компонент, который выполняет ту же функцию, что и EditText. Он позволяет вводить и редактировать текст.
Если ввести TextField, то можем увидеть и другие компоненты, помеченные значком Compose.
- OutlinedTextField имеет тот же функционал, что и
TextField, но поле отображается с обводкой (или с рамкой). - BasicTextField – более низкоуровневый компонент для текстового ввода, который дает больше контроля над стилизацией и поведением. Используется в основном для создания собственных текстовых полей.

Создание текстового поля OutlinedTextField
Выбираем OutlinedTextField, он просто больше нам подходит по стилю. Вы можете попробовать все варианты. Функция имеет множество специализированных параметров для настройки внешнего вида и поведения. Первые два обязательные.
value– это поле, которое должно отображать содержимое этого самого текстового поля. Не путать с плейсхолдером. Мы можем вписать сюда какой-нибудь текст и он будет отображаться в поле так, как будто его уже вписали заранее. Для примера что-то напишу сюда.onValueChange– работает точно также, как иonCheckedChangeна предыдущем уроке. Только там при клике в лямбду передавалось значение. Здесь же при каждом изменении текстового поля эта лямбда будет обновляться. Неважно вводим мы какой-то символ или удаляем.shape– скопирую кнопки, это добавит единого стиля.textStyle– добавлю по той же причине из переопределенных нами ранее стилей.
Вызовем функцию в setContent и посмотрим что получилось. Обратите внимание, из поля value строка отобразилась в текстовом поле. Курсор активируется, но ничего напечатать или стереть невозможно.
@Composable
@Preview(showBackground = true)
fun CheckEmailFields() {
OutlinedTextField(
value = "42 42 42",
onValueChange = {},
shape = RoundedCornerShape(13.dp),
textStyle = MaterialTheme.typography.headlineMedium,
)
}
Причина все та же, что и с чекбоксом. Compose не вызывает рекомпозицию, потому, что не знает в какой момент это нужно делать, да и не написан требуемый инструментарий. Воспользуемся аналогичным объектом MutableState в связке с remember. Импортируем делегат by и значение по умолчанию перенесу из value в стейт. И не забываем в лямбде при изменении текстового поля присваивать значения нашему стейту. Теперь можно проверять.
@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,
)
}
Добавление Placeholder
Отлично. Стейт подхватывает изменения при вводе каждого символа и вызывает рекомпозицию. Теперь уберу значение по умолчанию и добавлю плейсхолдер с помощью специализированного для OutlinedTextField параметра функции. Placeholder последним параметром принимает composable лямбду, поэтому строка там устанавливается обычной функцией Text с оформлением по нашим предпочтениям. Добавим стиля и цвета.
Добавление SingleLine
Работает хорошо, но если я буду нажимать enter, то по умолчанию текстовое поле будет увеличиваться.

Нам такое поведение не нужно, поэтому добавлю параметр singleLine = true. По умолчанию он установлен в положение false.
Добавление Label
Есть еще один крутой атрибут для стилизации – label. Точно также задаем какой-то текст внутри. На стиль не обращайте внимания, я его переопределил по собственным размерам. В итоге мы получаем вот такую анимацию лейбла при активации текстового поля. Ну, это красиво.

Добавление иконки с кнопкой в текстовое поле – IconButton
Следующая пара параметров отвечает за иконки внутри текстового поля. Из них можно сделать кнопки с каким-нибудь функционалом.
leadingIconдобавит иконку в начало поля,trailingIconдобавляет в конец.
Этот параметр принимает composable функцию, следовательно в нее отправляем то, что хотим отобразить. Пусть это будет IconButton – смотрим в декларацию, у этой функции первый параметр onClick обязательный. То есть что будет происходить при нажатии – оставим пока пустую лямбду.
Ну а чтобы добавить само изображение, нужно последним параметром IconButton – то есть в лямбде вызвать функцию Icon. Здесь принцип схож с добавлением картинки. Первым параметром задается сама иконка, взять ее можно из стандартного набора предоставляемого Material: imageVector = Icons.Filled.Clear. Второй параметр contentDescription тоже обязательный – добавим описание иконки. Готово.
Смотрим. Иконка отрисовалась, она кликабельна, все хорошо.

И мы можем задать ей какое-то полезное действие очень просто. Пусть она очищает введеный текст.
Как это сделать? Во-первых обратить внимание на пустую лямбду – здесь должно что-то происходить по клику на иконку. Во-вторых обратить внимание на изменяемую переменную со стейтом, которая при изменении вызывает рекомпозицию. Следовательно здесь достаточно просто задавать стейту пустую строку. И она сразу же отрисуется в TextField. Проверяем – и все происходит именно так. Великолепно.
trailingIcon = {
IconButton(
onClick = { textState = "" }
) {
Icon(
imageVector = Icons.Filled.Clear,
contentDescription = "Иконка очистки поля"
)
}
}
Валидация email и ErrorState
Раз уж мы оформляем поле для ввода электронной почты, стоит добавить валидацию. Проверять, что пользователь ввел действительно почту по шаблону. Для этого есть стандартные механизмы.
Для начала воспользуемся у нашей composable функции параметром isError. Если его установить в true и посмотреть что будет – обнаружим текстовое поле в состоянии “Ошибка”. Оно окрашено в красный.

Как правило в таком состоянии нельзя отправить форму, если данные отсутствуют или не верны. Мы просто внедрим логику валидации на корректность имейла.
Как правило в Compose все логично, то есть как слышится, так и пишется. Когда я говорил про состояние “Ошибка” – я буквально имел в виду, что нам нужно создать дополнительный стейт для хранения состояния ошибки. Сделаем это.
var errorState by remember { mutableStateOf("") }
Мы будем хранить строку, хотя в стейте можно хранить что угодно. Например, Boolean – флаг есть ошибка или нет, код конкретной полученной ошибки или целый объект, предварительно создав для него класс. То же самое относится и к обычным стейтам, по сути они ничем не отличаются, мы просто решили, что этот объект MutableState будет отвечать за хранение ошибок в данной функции.
Теперь самое интересное. Где валидировать введенный текст? Конечно, в onValueChange, после каждого введенного символа. Так что textState мы не трогаем, чтобы текст отрисовывался как положено. А далее присваиваем errorState следующий код.
errorState = if (EMAIL_ADDRESS.matcher(it).matches()) "" else "Некорректный email"
Код проверяет, соответствует ли введённый текст формату электронной почты с помощью регулярного выражения. Если текст правильный, то errorState устанавливается в пустую строку (что будет означать отсутствие ошибок), а если неправильный — присваивается сообщение «Введите корректный email», чтобы уведомить пользователя о необходимости исправить ввод.
Наконец, сейчас у нас isError захардкожен. Добавим запись о том, что показывать ошибку только если стейт с ошибкой не пустой (то есть не является пустой строкой) – isError = errorState.isNotEmpty(),.
Запускаем. Вот что получается. При вводе каждого символа происходит валидация строки и пока она не похожа на почту – в стейт записывается “Введите корректный email”.

Как только я добавляю в конце .ru – ошибка пропадает.
Давайте выведем в лейбл текст ошибки – пусть она отображается пока ошибка активна, а когда ошибки нет – показывается стандартный лейбл.
label = {
Text(
text = if (errorState.isEmpty()) "Электропочта" else errorState,
style = MaterialTheme.typography.headlineSmall,
)
},


Последний штрих – очищать errorState при клике на кнопку очистки поля, иначе сейчас очищается только стейт с текстом, а ошибка остается.

Добавлю errorState = "".
Выглядит довольно неплохо.

В следующем уроке мы продолжим развивать функционал экрана с регистрацией и конкретно рассмотрим как работают стейты не только в рамках одной функции, а для всего экрана.
Бесплатные Telegram-боты для обучения
Практика с проверкой кода и помощью ИИ-ментора
AndroidSprint AI Mentor
Проверяет Pull Request'ы в GitHub, проводит тестовые собеседования с голосом и таймером, помогает разбираться с кодом 24/7
Попробовать ИИ-ментора →KotlinSprint Bot
22 урока Kotlin, 220 тестов, 120 практических задач с код-ревью
Начать обучение Kotlin →