Нет лучшего способа освоить современную Android-разработку, чем написать что-то руками. В этом уроке из серии Android Studio: уроки на Kotlin мы создадим полноценный экран регистрации с проверкой вводимых данных, используя Jetpack Compose и Kotlin. Вы узнаете, как управлять состоянием полей, реализовать валидацию на лету, показывать ошибки и обрабатывать нажатие кнопки — и всё это в декларативном стиле, без XML.
Что мы будем строить и зачем
Цель урока — экран с тремя полями: имя, email и пароль. После нажатия кнопки «Зарегистрироваться» система проверяет корректность ввода и либо показывает приветствие, либо подсвечивает ошибочные поля с пояснениями. Такой компонент есть в каждом втором приложении, поэтому понимание его внутреннего устройства критически важно для любого, кто изучает Android Studio Kotlin уроки на практике.
Шаг 1: Создаём проект в Android Studio
Запустите Android Studio и выберите New Project. В шаблонах выберите Empty Activity и дайте проекту имя, например, RegistrationScreen. Убедитесь, что в качестве языка выбран Kotlin, а минимальный SDK установлен на API 24 или выше. Готовый шаблон уже содержит одну MainActivity и базовую настройку Compose — нам остаётся только наполнить его логикой.
Шаг 2: Знакомимся с состоянием в Compose
В Jetpack Compose интерфейс не обновляется напрямую — он перерисовывается при изменении состояния. Для хранения состояния отдельного поля мы будем использовать remember { mutableStateOf(...) }. Это гарантирует, что значение сохранится между перекомпоновками и его изменение вызовет перерисовку только тех частей экрана, которые от него зависят.
Переменные состояния для полей
Объявим три переменные внутри нашей компонуемой функции:
var name by remember { mutableStateOf("") }
var email by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
Ключевое слово by позволяет работать с состоянием как с обычной переменной, а делегат remember сохраняет его при рекомпозиции.
Шаг 3: Верстаем экран регистрации
Расположим поля и кнопку в вертикальной колонке с отступами. Каждое текстовое поле обернём в OutlinedTextField — стандартный компонент Material 3. Не забудем про лейблы и визуальные разделители.
Column(
modifier = Modifier
.fillMaxSize()
.padding(24.dp),
verticalArrangement = Arrangement.Center
) {
Text("Регистрация", style = MaterialTheme.typography.headlineMedium)
Spacer(modifier = Modifier.height(16.dp))
OutlinedTextField(
value = name,
onValueChange = { name = it },
label = { Text("Имя") },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(8.dp))
OutlinedTextField(
value = email,
onValueChange = { email = it },
label = { Text("Email") },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(8.dp))
OutlinedTextField(
value = password,
onValueChange = { password = it },
label = { Text("Пароль") },
visualTransformation = PasswordVisualTransformation(),
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(16.dp))
Button(
onClick = { /* скоро добавим */ },
modifier = Modifier.fillMaxWidth()
) {
Text("Зарегистрироваться")
}
}
Для пароля используем PasswordVisualTransformation(), чтобы скрыть символы. Импорт этой функции уже есть в Compose.
Шаг 4: Валидация полей — умная и наглядная
Теперь самое интересное. Нам нужна логика, которая проверяет поля при нажатии кнопки и запоминает ошибки. Для этого заведём ещё одну переменную состояния — errorMessages — и будем наполнять её при попытке регистрации.
var errorMessages by remember { mutableStateOf(mapOf<String, String>()) }
Ключами будут названия полей (name, email, password), а значениями — текст ошибки. При каждом нажатии кнопки мы сбрасываем предыдущие ошибки и формируем новый словарь.
Функция валидации
Напишем отдельную функцию, которая возвращает true, только если все поля прошли проверку:
fun validate(): Boolean {
val errors = mutableMapOf<String, String>()
if (name.isBlank()) errors["name"] = "Имя обязательно"
if (email.isBlank() || !android.util.Patterns.EMAIL_ADDRESS.matcher(email).matches())
errors["email"] = "Введите корректный email"
if (password.length < 6) errors["password"] = "Пароль должен быть не менее 6 символов"
errorMessages = errors.toMap()
return errors.isEmpty()
}
Обратите внимание: toMap() копирует данные, чтобы гарантировать изменение состояния.
Шаг 5: Показываем ошибки под полями
Каждому OutlinedTextField можно передать параметр isError и дополнительный слот supportingText. Если для поля есть ошибка, мы включим флаг и выведем текст.
OutlinedTextField(
value = name,
onValueChange = { name = it; errorMessages = errorMessages - "name" },
isError = errorMessages.containsKey("name"),
supportingText = { errorMessages["name"]?.let { Text(it) } },
// остальные параметры
)
Когда пользователь начинает вводить символ в ошибочное поле, мы сразу убираем сообщение для этого поля — простой, но очень дружелюбный UX.
Шаг 6: Обработка успешной регистрации
Внутри onClick кнопки вызовем validate(). Если метод вернул true, покажем приветственный Snackbar с помощью SnackbarHostState и корутины. Для этого добавим val snackbarHostState = remember { SnackbarHostState() } и обернём Scaffold с snackbarHost.
val scope = rememberCoroutineScope()
val snackbarHostState = remember { SnackbarHostState() }
Scaffold(snackbarHost = { SnackbarHost(snackbarHostState) }) { paddingValues ->
// контент с paddingValues
Button(onClick = {
if (validate()) {
scope.launch {
snackbarHostState.showSnackbar("Добро пожаловать, $name!")
}
}
}) { ... }
}
Теперь ошибки отображаются только при некорректном вводе, а в случае успеха мы приветствуем пользователя по имени.
Полный код MainActivity
Чтобы вам было проще, вот итоговая версия экрана регистрации. Скопируйте её в свой проект и запустите.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme {
RegistrationScreen()
}
}
}
}
@Composable
fun RegistrationScreen() {
var name by remember { mutableStateOf("") }
var email by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
var errorMessages by remember { mutableStateOf(mapOf<String, String>()) }
val snackbarHostState = remember { SnackbarHostState() }
val scope = rememberCoroutineScope()
fun validate(): Boolean {
val errors = mutableMapOf<String, String>()
if (name.isBlank()) errors["name"] = "Имя обязательно"
if (email.isBlank() || !android.util.Patterns.EMAIL_ADDRESS.matcher(email).matches())
errors["email"] = "Введите корректный email"
if (password.length < 6) errors["password"] = "Пароль должен быть не менее 6 символов"
errorMessages = errors.toMap()
return errors.isEmpty()
}
Scaffold(snackbarHost = { SnackbarHost(snackbarHostState) }) { paddingValues ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
.padding(24.dp),
verticalArrangement = Arrangement.Center
) {
Text("Регистрация", style = MaterialTheme.typography.headlineMedium)
Spacer(modifier = Modifier.height(16.dp))
OutlinedTextField(
value = name,
onValueChange = { name = it; errorMessages = errorMessages - "name" },
isError = errorMessages.containsKey("name"),
supportingText = { errorMessages["name"]?.let { Text(it) } },
label = { Text("Имя") },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(8.dp))
OutlinedTextField(
value = email,
onValueChange = { email = it; errorMessages = errorMessages - "email" },
isError = errorMessages.containsKey("email"),
supportingText = { errorMessages["email"]?.let { Text(it) } },
label = { Text("Email") },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(8.dp))
OutlinedTextField(
value = password,
onValueChange = { password = it; errorMessages = errorMessages - "password" },
isError = errorMessages.containsKey("password"),
supportingText = { errorMessages["password"]?.let { Text(it) } },
label = { Text("Пароль") },
visualTransformation = PasswordVisualTransformation(),
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(16.dp))
Button(
onClick = {
if (validate()) {
scope.launch {
snackbarHostState.showSnackbar("Добро пожаловать, $name!")
}
}
},
modifier = Modifier.fillMaxWidth()
) {
Text("Зарегистрироваться")
}
}
}
}
Чему вы научились
Этот урок из цикла Android Studio: Kotlin уроки дал вам практические навыки работы с состоянием в Jetpack Compose, валидацией форм, отображением динамических ошибок и использованием Snackbar. Вы написали чистое, структурированное приложение, которое можно сразу положить в портфолио. Понимание этих основ открывает дорогу к созданию более сложных экранов — авторизации, профилей, настроек — и формирует правильное мышление декларативного UI.