Kodokon kodokon.com

Формы: TextField, контроллеры и валидация

Управляй полями ввода через TextEditingController и проверяй все данные разом благодаря Form и TextFormField.

9 мин · 3 вопросов

Открыть этот урок в Kodokon

Поле ввода без контроллера - чёрный ящик. TextEditingController отдаёт управление тебе: читай controller.text в любой момент, заполняй поле заранее, очищай его после отправки или слушай каждое нажатие клавиши через addListener. Контроллер создаётся в State, никогда в build - иначе он пересоздавался бы при каждом перестроении.

DART
import 'package:flutter/material.dart';

class NicknameField extends StatefulWidget {
  const NicknameField({super.key});

  @override
  State<NicknameField> createState() =>
      _NicknameFieldState();
}

class _NicknameFieldState extends State<NicknameField> {
  final _controller = TextEditingController();

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return TextField(
      controller: _controller,
      decoration: const InputDecoration(
        labelText: 'Nickname',
        border: OutlineInputBorder(),
      ),
      onSubmitted: (value) => debugPrint(value),
    );
  }
}
Контроллер живёт в State и умирает в dispose().

Как только на экране появляется несколько полей для проверки, бери на вооружение троицу Form, GlobalKey<FormState> и TextFormField. Каждое поле описывает своё правило в validator: функция возвращает сообщение об ошибке или null, если всё в порядке. Вызов validate() у ключа запускает все валидаторы разом.

DART
import 'package:flutter/material.dart';

class SignUpForm extends StatefulWidget {
  const SignUpForm({super.key});

  @override
  State<SignUpForm> createState() => _SignUpFormState();
}

class _SignUpFormState extends State<SignUpForm> {
  final _formKey = GlobalKey<FormState>();
  final _emailController = TextEditingController();

  @override
  void dispose() {
    _emailController.dispose();
    super.dispose();
  }

  void _submit() {
    if (_formKey.currentState!.validate()) {
      debugPrint('Email: ${_emailController.text}');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Form(
      key: _formKey,
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          TextFormField(
            controller: _emailController,
            keyboardType: TextInputType.emailAddress,
            decoration: const InputDecoration(
              labelText: 'Email',
            ),
            validator: (value) {
              if (value == null || value.isEmpty) {
                return 'Required field';
              }
              if (!value.contains('@')) {
                return 'Invalid address';
              }
              return null;
            },
          ),
          const SizedBox(height: 16),
          FilledButton(
            onPressed: _submit,
            child: const Text('Create account'),
          ),
        ],
      ),
    );
  }
}
validate() запускает каждый валидатор формы.

При нажатии на кнопку validate() выполняет каждый validator: возвращённые сообщения появляются красным под соответствующими полями, а метод возвращает false, если хотя бы одно поле некорректно. Твоя логика отправки остаётся за одним-единственным if - отправить недопустимые данные невозможно.

Проверка знаний

Убедись, что запомнил ключевые моменты этого урока.

  1. Для чего нужен TextEditingController?
    • Чтобы рисовать рамку поля
    • Чтобы читать и изменять текст поля, а также слушать его изменения
    • Чтобы автоматически проверять адреса e-mail
    • Чтобы закрывать клавиатуру
  2. Что должен вернуть валидатор, когда значение корректно?
    • true
    • Пустую строку
    • null
    • Само значение
  3. Зачем вызывать _controller.dispose() в методе dispose() у State?
    • Чтобы очистить поле на экране
    • Чтобы освободить ресурсы и избежать утечек памяти
    • Чтобы отправить форму