Перейти к основному содержимому
Мобилка20ГЛАВА 20Поля ввода: подпись, ошибка и клавиатураПочтаoleg@pgk63.ruПароль••••••Нужен пробелvalue ↑ onValueChange ↓текст живёт снаружи

Поля ввода: подпись, ошибка и клавиатура

Прочитано0%
Квизы0/4
Тренажёрыне пройден0/1
Уровень 1 · Новичок

Зачем это нужно​

«Создайте инпут» — один пункт критериев, а работы в нём больше, чем в кнопке: у поля ввода четыре состояния на макете, своя клавиатура под каждый тип данных и отдельный вопрос — когда показывать ошибку.

Последнее оценивается вживую. Если приложение ругается «неверная почта» на первую же набранную букву, это видно эксперту сразу и выглядит именно тем, чем является, — недоделкой.

Как и в прошлой главе, весь код собран настоящим компилятором, а сообщения об ошибках — его подлинный вывод.

Поле не хранит текст​

ПОЛЕ НЕ ХРАНИТ ТЕКСТ — ОНО ЕГО ПОКАЗЫВАЕТСостояние экранаvar email by remember{ mutableStateOf("") }valueonValueChangeKitTextFieldрисует то, что далисообщает о нажатияхРазомкнули кольцоvalue = "текст"поле просто не печатаетсятекст живёт снаружи; поле получает его сверху и отдаёт изменения обратноразорвать кольцо легко, и компилятор про это ничего не скажет — код законный«не печатается» почти всегда значит: onValueChange не меняет то, что уходит в value
Текст живёт снаружи: поле получает его сверху и отдаёт изменения обратно

Это первое, что ломает привычку тех, кто писал на вёрстке через XML. Поле ввода в Compose ничего не помнит. Оно получает строку в value, рисует её и сообщает о нажатиях через onValueChange. Хранить текст — обязанность вызывающего.

var email by remember { mutableStateOf("") }

KitTextField(
value = email,
onValueChange = { email = it },
label = "Почта",
)
Нажми на часть выражения

А теперь то, ради чего эта схема тут:

@Composable
fun P() {
OutlinedTextField(value = "текст", onValueChange = {})
}
(сборка проходит, ошибок нет)

Компилятор доволен: типы сошлись. А поле не печатается — в него можно тыкать сколько угодно, текст не меняется, потому что value всегда одна и та же константа, а onValueChange ничего не делает.

Важно

«Поле не печатается» — самая частая жалоба на первом дне работы, и причина всегда одна: кольцо разомкнуто. Либо onValueChange не меняет состояние, либо в value уходит не то состояние, которое он меняет. Компилятор здесь не помощник: код законный.

Компонент библиотеки​

@Composable
fun KitTextField(
value: String,
onValueChange: (String) -> Unit,
label: String,
modifier: Modifier = Modifier,
placeholder: String? = null,
error: String? = null,
enabled: Boolean = true,
singleLine: Boolean = true,
keyboardType: KeyboardType = KeyboardType.Text,
imeAction: ImeAction = ImeAction.Next,
visualTransformation: VisualTransformation = VisualTransformation.None,
trailing: (@Composable () -> Unit)? = null,
) {
OutlinedTextField(
value = value,
onValueChange = onValueChange,
modifier = modifier,
enabled = enabled,
singleLine = singleLine,
isError = error != null,
label = { Text(label) },
placeholder = placeholder?.let { { Text(it) } },
supportingText = error?.let { { Text(it) } },
trailingIcon = trailing,
visualTransformation = visualTransformation,
keyboardOptions = KeyboardOptions(keyboardType = keyboardType, imeAction = imeAction),
)
}
Нажми на часть выражения
Совет

Приём с error: String? вместо пары isError: Boolean и errorText: String стоит запомнить: он делает невозможным рассогласование. Нельзя случайно получить красную рамку без объяснения или объяснение без красной рамки.

Отвечено вопросов: 0 из 3
Поле не печатается, хотя код компилируется. В чём причина?
Почему ошибку передают одним параметром error: String?, а не парой isError и errorText?
Что означает it в onValueChange = { email = it }?
Отвечено верно: 0 из 3

Четыре состояния с макета​

ОДНО ПОЛЕ, ЧЕТЫРЕ СОСТОЯНИЯ НА МАКЕТЕivan@mail.ruПустоеподпись наверху, подсказка внутриoleg@pgk63.ruЗаполненноеподпись уехала наверх и уменьшиласьoleg@С ошибкойрамка и подпись красные, текст под полемнельзя менятьОтключённоеприглушённое, не принимает вводвсе четыре — один компонент с разными параметрами, а не четыре разных
Все четыре — один компонент с разными параметрами

На макете поле обычно нарисовано четыре раза, и это не четыре компонента:

СостояниеЧто передаём
Пустоеvalue = "", placeholder = "ivan@mail.ru"
Заполненноеvalue = "oleg@pgk63.ru"
С ошибкойerror = "Неверный формат почты"
Отключённоеenabled = false
Интересный факт

Подпись (label) в Material 3 ведёт себя хитро: у пустого поля она стоит внутри как текст, а при вводе уезжает наверх и уменьшается. Подсказка (placeholder) видна только у пустого поля и только когда подпись уже уехала. Это частый источник вопроса «почему не видно placeholder» — он перекрыт подписью, пока в поле не встали курсором.

Пароль: то же поле, другие правила​

@Composable
fun KitPasswordField(
value: String,
onValueChange: (String) -> Unit,
modifier: Modifier = Modifier,
label: String = "Пароль",
error: String? = null,
visible: Boolean = false,
trailing: (@Composable () -> Unit)? = null,
) {
KitTextField(
value = value,
onValueChange = onValueChange,
label = label,
modifier = modifier,
error = error,
keyboardType = KeyboardType.Password,
imeAction = ImeAction.Done,
visualTransformation = if (visible) VisualTransformation.None else PasswordVisualTransformation(),
trailing = trailing,
)
}

Поле пароля — не новый компонент, а настройка существующего. Три отличия: своя клавиатура, Done вместо Next (пароль обычно последний) и преобразование показа — текст остаётся в состоянии как есть, а на экран выводятся точки.

Важно

visualTransformation меняет только вид. В value по-прежнему лежит настоящий пароль, и именно он уходит в проверку и в сеть. Путать эти две вещи опасно: попытка «взять то, что показано» даст строку из точек.

Кнопка «показать пароль» делается слотом — компонент про неё не знает:

KitPasswordField(
value = password,
onValueChange = { password = it },
visible = shown,
trailing = {
KitButtonGhost(text = if (shown) "Скрыть" else "Показать", onClick = { shown = !shown })
},
)

Когда показывать ошибку​

КОГДА ПОКАЗЫВАТЬ ОШИБКУСразу при вводеo → «неверная почта»ol → «неверная почта»ole → «неверная почта»человек ещё печатает, а его уже ругаютПосле первого ухода из поляo, ol, ole… — тихоушёл из поля → проверилидальше — на каждый символошибка появляется вовремя и исчезает сразуправило: первый раз проверяем при уходе из поля, дальше — на каждое изменениеиначе либо ругань на первую букву, либо ошибка, которая не уходит после починки
Первый раз — при уходе из поля, дальше — на каждое изменение

Правило, которое решает большинство претензий на приёмке:

  • пока человек ещё не уходил из поля — молчим, что бы он ни набрал;
  • как только ушёл (нажал на другое поле, свернул клавиатуру) — проверяем и показываем;
  • дальше проверяем на каждое изменение, чтобы ошибка исчезла сразу после починки.

Логику проверки удобно держать отдельно от разметки — её и протестировать проще:

fun pochtaOshibka(v: String): String? = when { v.isBlank() -> "Укажите почту" !v.contains("@") -> "В адресе нет знака @" v.substringAfter("@").isBlank() -> "После @ ничего нет" !v.substringAfter("@").contains(".") -> "В домене нет точки" else -> null } fun main() { for (v in listOf("", "oleg", "oleg@", "oleg@mail", "oleg@mail.ru")) { println("\"" + v + "\" -> " + (pochtaOshibka(v) ?: "ок")) } }

А в разметке — только момент показа:

var email by remember { mutableStateOf("") }
var trogali by remember { mutableStateOf(false) }

val oshibka = if (trogali) pochtaOshibka(email) else null

KitTextField(
value = email,
onValueChange = { email = it },
label = "Почта",
error = oshibka,
modifier = Modifier.onFocusChanged { if (!it.isFocused) trogali = true },
)
Нажми на часть выражения
Отвечено вопросов: 0 из 3
Когда показывать ошибку в поле первый раз?
Что лежит в value у поля пароля с visualTransformation?
Зачем отдельное состояние «трогали ли поле»?
Отвечено верно: 0 из 3

Что ловит компилятор, а что нет​

Подпись строкой вместо разметки:

OutlinedTextField(value = "", onValueChange = {}, label = "Почта")
e: Probe.kt:4:23 None of the following candidates is applicable:

То же самое сообщение, что и в главе про макет при fontSize = 16.dp, и читается так же: ни одна перегрузка не подошла, ищите свой параметр в сигнатуре. Там будет label: (@Composable () -> Unit)? — то есть нужна разметка, а не строка. Ровно так же отказывает supportingText = "ошибка".

Опечатка в перечислении:

KeyboardOptions(keyboardType = KeyboardType.Emal)
e: Probe.kt:5:75 Unresolved reference 'Emal'.

А это компилятор пропустит:

  • value из константы — поле не печатается;
  • KeyboardType.Text там, где нужен Number — человек будет искать цифры на буквенной клавиатуре;
  • imeAction = ImeAction.Next на последнем поле формы — кнопка переведёт в никуда;
  • singleLine = true у поля адреса — длинный адрес не будет виден целиком.
Интересный факт

Ни одна из четырёх последних ошибок не видна в коде — они видны только на устройстве. Поэтому каждое поле формы прокликивают руками: набрать, уйти, вернуться, отправить. Три минуты на форму.

Типичные ошибки​

Ошибка 1. Состояние без remember.

Текст сбрасывается при каждой перерисовке: поле «печатает и стирает». Компилятор молчит.

Ошибка 2. Проверка прямо в разметке.

error = if (!email.contains("@")) "Неверно" else null

Ругань на первую букву. Нужен признак «трогали».

Ошибка 3. Ошибка не исчезает после починки.

Обратная крайность: проверили один раз при уходе и запомнили результат. Человек исправил адрес, а красная рамка висит. После первого показа проверять надо на каждое изменение.

Ошибка 4. Клавиатура не под тип данных.

Телефон, почта и количество набираются на разных клавиатурах. Это один параметр, и он оценивается.

Ошибка 5. Пароль виден в отладочном выводе.

Log.d("form", "пароль: $password")

Журнал показывают на демонстрации, а иногда прикладывают к сдаче.

Ошибка 6. trailing занят иконкой, а нужен для «показать пароль».

Слот один. Если туда уже положили иконку по макету, кнопку показа кладут в него же — вместе с иконкой в Row.

Отвечено вопросов: 0 из 3
Человек исправил адрес, а красная рамка не исчезла. Что не так?
label = "Почта" не компилируется. Как читать «None of the following candidates is applicable»?
Какую из этих ошибок компилятор НЕ поймает?
Отвечено верно: 0 из 3

Шпаргалка по главе​

Что нужноКак
Текст поляхранить снаружи: var v by remember { mutableStateOf("") }
Связатьvalue = v, onValueChange = { v = it }
Подпись и подсказкаlabel = { Text(...) }, placeholder = { Text(...) } — разметка, не строка
Ошибкаодин параметр error: String?, isError вывести внутри
КлавиатураKeyboardOptions(keyboardType = ..., imeAction = ...)
Скрыть парольvisualTransformation = PasswordVisualTransformation()
Момент проверкипризнак «трогали» + Modifier.onFocusChanged
Проверить рукаминабрать, уйти, вернуться, исправить, отправить

Слова главы​

ТерминЧто это
Управляемое полеПоле, которое не хранит текст: получает value, отдаёт onValueChange
labelПодпись; у пустого поля внутри, при вводе уезжает наверх
placeholderПодсказка; видна только у пустого поля
supportingTextТекст под полем: пояснение или ошибка
visualTransformationМеняет только вид текста; в состоянии остаётся исходный
imeActionЧто написано на кнопке клавиатуры: Next или Done
Признак «трогали»Состояние, по которому решают, показывать ли ошибку

Цель: скорость ≥ 95 зн/мин, точность ≥ 90%

var email by remember { mutableStateOf("") }

Что должен уметь после главы​

  • объяснить, почему поле не хранит текст, и починить «поле не печатается»;
  • собрать компонент поля с подписью, подсказкой, ошибкой и клавиатурой;
  • передать ошибку одним параметром и объяснить, почему не парой;
  • показать четыре состояния поля одним компонентом;
  • сделать поле пароля и не перепутать показанное со значением;
  • выбрать момент показа ошибки и объяснить обе крайности;
  • назвать, что здесь компилятор не проверяет вовсе.

Проверка по главе​

Сводная проверка по всей главе: 10 вопросов, 5:00 на всё, без подсказок по ходу. Вопросы показываются по одному, ответ изменить нельзя. Если время выйдет — экзамен завершится с тем, что успел ответить. Пересдавать можно сколько угодно раз.

Куда дальше: проверенные ресурсы​

Видео по теме

ВидеоУрок 9: TextField, OutlinedTextField, Валидация почты, ErrorState | Курс Android Jetpack ComposeKotlin & Android с нуля – Иван Ветров
ВидеоForm Validation in Jetpack Compose. #JetpackCompose #Kotlin #Android #MobileDevelopment #uiuxthemobileXplorer - Saurabh
ВидеоText Fields - Jetpack ComposeStevdza-San
ВидеоSecure Login Form UI | TextField Components #2 | Jetpack Compose Material 3RAN MasterApps
ВидеоStyling TextField in Jetpack ComposeMawuli Azameti

Комментарии

Обсуждение живёт в GitHub Discussions и грузится с чужого домена — поэтому только по нажатию.