Irbisa · Шпаргалка

Формы, ввод и фокус

TextField, TextEditingController, валидация Form, FocusNode, форматтеры ввода, клавиатура

Junior-разработчик · 21 вопрос

  1. 01

    Что такое TextEditingController и почему его нужно создавать в State, а не в build?

    TextEditingController хранит текущее значение поля (текст, выделение и диапазон, который ещё набирает IME) и позволяет читать, задавать и слушать его.

    class NameField extends StatefulWidget {  const NameField({super.key});  @override  State<NameField> createState() => _NameFieldState();}…
  2. 02

    Чем TextField отличается от TextFormField?

    TextFormField представляет собой TextField, обёрнутый в FormField<String>: он регистрируется в ближайшем Form и добавляет validator, onSaved, initialValue и autovalidateMode, показывая сообщение валидатора как ошибку поля.

  3. 03

    Как валидировать форму во Flutter: за что отвечают Form, GlobalKey<FormState> и validator?

    Форма объединяет TextFormField-ы, GlobalKey<FormState> даёт доступ к её состоянию, а validator каждого поля возвращает текст ошибки или null.

    class _LoginFormState extends State<LoginForm> {  final _formKey = GlobalKey<FormState>();  String _email = '';   void _submit() {    final form = _formKey.currentState!;…
  4. 04

    Чем отличаются onChanged, onSubmitted и слушатель TextEditingController?

    onChanged срабатывает, когда пользователь редактирует текст, onSubmitted срабатывает, когда он нажимает клавишу действия на клавиатуре, а слушатель контроллера срабатывает на любое изменение значения, включая перемещение каретки и текст, заданный из кода.

    class _SearchState extends State<Search> {  final _query = TextEditingController();  String _last = '';   @override  void initState() {…
  5. 05

    Что делают labelText, hintText, helperText и errorText в InputDecoration и почему форма «прыгает», когда появляется ошибка?

    Все четыре рисует InputDecorator внутри блока самого поля.

  6. 06

    За что отвечают параметры keyboardType, textInputAction и textCapitalization у поля TextField?

    Они настраивают экранную клавиатуру, а не значение: keyboardType выбирает раскладку, textInputAction выбирает клавишу действия, textCapitalization задаёт состояние shift.

  7. 07

    Что делает Scaffold.resizeToAvoidBottomInset и почему клавиатура всё равно может закрыть кнопку формы?

    При resizeToAvoidBottomInset: true (значение по умолчанию) Scaffold уменьшает body на высоту клавиатуры.

    Scaffold(  // resizeToAvoidBottomInset: true is the default; leave it on  body: SafeArea(    child: SingleChildScrollView(      padding: const EdgeInsets.all(16),      child: Column(…
  8. 08

    validate() прошёл, вы вызвали save(), а модель всё равно пустая. Где Form на самом деле хранит значения?

    Form значений не хранит.

    class _CheckoutState extends State<Checkout> {  final _formKey = GlobalKey<FormState>();  var _order = const Order();   void _submit() {    final form = _formKey.currentState!;…
  9. 09

    Найдите баг: форма редактирования профиля показывает пустое поле Name даже после того, как профиль загрузился.

    class _ProfileFormState extends State<ProfileForm> {  Profile? _profile;   @override  void initState() {    super.initState();    widget.repo.load().then((p) { if (mounted) setState(() => _profile = p); });  }   @override  Widget build(BuildContext context) => TextFormField(        initialValue: _profile?.name,        decoration: const InputDecoration(labelText: 'Name'),      );}

    initialValue читается только один раз: когда создаётся FormFieldState поля.

  10. 10

    Форма регистрации открывается уже вся в красных ошибках. Какой autovalidateMode это даёт и что использовать вместо него?

    Это AutovalidateMode.always: он запускает все валидаторы на каждом build, включая первый, поэтому нетронутая форма уже полна ошибок.

  11. 11

    Ошибку сервера «email уже зарегистрирован» показывают так, и она остаётся после того, как пользователь исправил email. Почему и как исправить?

    String? _serverError; // set when the API rejects the email TextFormField(  controller: _email,  autovalidateMode: AutovalidateMode.onUserInteraction,  decoration: InputDecoration(    labelText: 'Email',    errorText: _serverError,  ),  validator: (v) => (v ?? '').contains('@') ? null : 'Enter a valid email',)

    decoration.errorText хранит ваше собственное состояние, а не состояние формы: TextFormField подставляет сообщение валидатора, только когда оно есть, а _serverError при наборе никто не сбрасывает.

  12. 12

    Что такое FocusNode и как перевести фокус на следующее поле и скрыть клавиатуру?

    FocusNode задаёт место поля в дереве фокуса, которое существует рядом с деревом виджетов; ввод с клавиатуры получает узел с основным фокусом.

    class _LoginState extends State<Login> {  final _password = FocusNode();   void _hideKeyboard(PointerDownEvent _) =>      FocusManager.instance.primaryFocus?.unfocus();…
  13. 13

    На клавиатуре есть клавиша Next, но после нажатия курсор остаётся в поле email. Что не так с этим кодом?

    Column(  children: [    TextField(      controller: _email,      textInputAction: TextInputAction.next,      onEditingComplete: _saveDraft,    ),    TextField(controller: _password, obscureText: true),  ],)

    Если передать onEditingComplete, он заменяет обработку клавиши действия по умолчанию, а именно она вызывает nextFocus() для TextInputAction.next.

  14. 14

    Как сделать маску ввода через TextInputFormatter и почему каретка после каждого нажатия прыгает в конец?

    formatEditUpdate(oldValue, newValue) форматтера возвращает целиком TextEditingValue, то есть решает и то, где стоит каретка.

    class ThousandsFormatter extends TextInputFormatter {  @override  TextEditingValue formatEditUpdate(TextEditingValue old, TextEditingValue next) {    final digits = next.text.replaceAll(RegExp(r'\D'), '');    if (digits.isEmpty) return const TextEditingValue();…
  15. 15

    validator обязан быть синхронным. Как показывать серверную проверку вроде «имя занято», пока пользователь печатает?

    Сетевой вызов не должен быть в валидаторе.

    class _UsernameFieldState extends State<UsernameField> {  Timer? _debounce;  int _request = 0;  String? _taken; // what the synchronous validator hands back  void _onChanged(String value) {    _taken = null; // the old verdict was about the old text…
  16. 16

    Выбор даты стоит внутри Form, но validate() его игнорирует, а save() не видит значение. Как сделать нетекстовый виджет настоящим полем формы?

    Унаследуйтесь от FormField<T>.

    class DateFormField extends FormField<DateTime> {  DateFormField({    super.key,    super.initialValue,    super.validator,    super.onSaved,…
  17. 17

    Менеджер паролей ОС не предлагает ни заполнить, ни сохранить форму входа, а код из SMS вводят руками. Что нужно автозаполнению во Flutter?

    Автозаполнение строится на контракте с ОС: полям нужны autofillHints, они должны лежать внутри одного AutofillGroup, а после успешного входа стоит завершить контекст автозаполнения.

    AutofillGroup( // these fields are ONE credential  child: Column(    children: [      TextField(        controller: _email,        autofillHints: const [AutofillHints.username], // not .email…
  18. 18

    Поле поиска управляется Bloc-ом, при быстром наборе теряются символы, а каретка прыгает в конец. Кто должен владеть текстом?

    Текстом и кареткой должен владеть TextEditingController.

    class _SearchFieldState extends State<SearchField> {  final _ctrl = TextEditingController();   @override  void dispose() {    _ctrl.dispose();…
  19. 19

    Текст, введённый в форму, пропадает после поворота телефона. Что было уничтожено и когда на самом деле нужен RestorableTextEditingController?

    Поворот здесь ни при чём: во Flutter это просто изменение размера, и каждый State его переживает.

    class _SignupPageState extends State<SignupPage> with RestorationMixin {  // survives process death; a plain controller survives only rebuilds  final _email = RestorableTextEditingController();   @override  String? get restorationId => 'signup'; // null opts this subtree out…
  20. 20

    Форма из 30 полей на ListView.builder теряет введённые значения и сообщения об ошибках, как только поле уезжает за экран. В чём причина и что менять?

    ListView.builder размонтирует дочерние элементы, которые ушли за пределы области кэша, и всё, чем они владели, умирает вместе с ними: TextEditingController строки и её FormFieldState.

    class _LongFormState extends State<LongForm> {  final _formKey = GlobalKey<FormState>();  // the page owns the values; a row is just a view of one  late final Map<String, TextEditingController> _ctrls = {    for (final f in widget.fields) f.id: TextEditingController(text: f.initial),  };…
  21. 21

    Этот форматтер, переводящий текст в верхний регистр, портит слова при наборе с предиктивной клавиатурой и японским IME. Что такое TextEditingValue.composing и что здесь не так?

    class ShoutFormatter extends TextInputFormatter {  @override  TextEditingValue formatEditUpdate(TextEditingValue old, TextEditingValue next) {    return TextEditingValue(      text: next.text.toUpperCase(),      selection: next.selection,      composing: next.composing,    );  }}

    Пока IME составляет слово, поле хранит предварительный текст, помеченный composing, то есть диапазоном, которым владеет платформа.