Введение
Это будет очень конкретная статья. Несколько дней назад один пользователь спросил меня, как он может реализовать проверку формы с динамически добавляемыми полями, поэтому я написал пример и теперь хочу показать вам, как я это реализовал.
Экран содержит:
- Список пар полей «Имя» и «Возраст» создается динамически (первые три пары создаются программно).
- Кнопка для каждой пары, чтобы удалить их из списка.
- Кнопка для добавления дополнительных пар полей.
- Кнопка отправки.
Часть 1. Настройка проекта
1 - Создайте новый проект флаттера:
flutter create your_project_name
2 - Отредактируйте файл pubspec.yaml и добавьте пакеты frideos:
dependencies: flutter: sdk: flutter frideos: ^0.6.1
3 - Удалите содержимое файла main.dart
и напишите следующее:
import ‘package:flutter/material.dart’; import ‘dynamic_fields.dart’; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: ‘Flutter Demo’, theme: ThemeData( primarySwatch: Colors.blue, ), home: DynamicFieldsPage(), ); } }
Это будет точка входа в пример, а DynamicFieldsPage
виджет, содержащий основные виджеты.
В примере есть еще два файла:
bloc.dart
: файл класса BLoC. В этом случае для простоты он объявлен глобальным экземпляром этого класса.dynamic_field.dart
: он содержит классDynamicFieldsPage
(это StatefulWidget),FieldsWidget
(Stateless) иDynamicFieldsWidget
(Stateless).
Часть 2 - класс BLoC
Свойства
Для обработки всех полей объявляется StreamedList
из StreamedValue<String>
для каждого из двух типов полей, имени и возраста. Это необходимо для того, чтобы можно было добавлять новые поля, связанные с потоком, и обрабатывать их проверку (см. Метод checkForm
).
final nameFields = StreamedList<StreamedValue<String>>(initialData: []); final ageFields = StreamedList<StreamedValue<String>>(initialData: []);
Свойство isFormValid
(aStramedValue<bool>
) используется для потоковой передачи текущего состояния формы. Это будет использоваться для управления виджетом StreamBuilder
в DynamicFieldsWidget
(объяснено в следующем шаге), чтобы включить или отключить кнопку отправки.
final isFormValid = StreamedValue<bool>();
Конструктор
В конструкторе оба StreamedList
инициализируются тремя StreamedValue<String>
каждый, так что приложение запускается с тремя парами полей имени / возраста, заполненными некоторыми значениями.
Затем методу onChange
каждого StreamedValue
назначается метод checkForm
, чтобы запускать проверку всех полей каждый раз, когда изменяется одно поле.
DynamicFieldsBloc() { print(‘ — — — -DynamicFields BLOC — — — — ‘); // Adding the initial three pairs of fields to the screen nameFields.addAll([ StreamedValue<String>(initialData: ‘Name AA’), StreamedValue<String>(initialData: ‘Name BB’), StreamedValue<String>(initialData: ‘Name CC’) ]); ageFields.addAll([ StreamedValue<String>(initialData: ‘11’), StreamedValue<String>(initialData: ‘22’), StreamedValue<String>(initialData: ‘33’) ]); // Set the method to call every time the stream emits a new event for (var item in nameFields.value) { item.onChange(checkForm); } for (var item in ageFields.value) { item.onChange(checkForm); } }
checkForm
Метод checkForm
вызывается каждый раз, когда поле изменяется, и определяет наличие недействительных полей. В этом примере для полей имени просто проверяется, не пуста ли строка, в противном случае в поток отправляется ошибка. Вместо этого для полей возраста проверяется, если значение не равно нулю и находится в диапазоне от 1 до 130.
Для логических переменных isValidFieldsTypeName
и isValidFieldsTypeAge
по умолчанию установлено значение true: соответствующее логическое значение устанавливается в значение false, если поле этого типа (имя или возраст) недопустимо.
В последних строках проверяется, истинны ли оба этих логических значения: это означает, что все поля действительны. В этом случае для isFormValid
(a StreamedType<bool>
) установлено значение true, в поток отправляется истинное событие, запускающее StreamBuilder
для восстановления виджета с новым значением, активируя кнопку отправки. В противном случае, если хотя бы одно поле недействительно, условие if
не выполняется и isFormValid
устанавливается в значение false (кнопка отправки будет отключена).
newFields
Этот метод используется для добавления на экран новой пары полей имени / возраста. В первых двух строках новый StreamedValue<String>
добавляется к nameFields и ageFields StreamedList
, а егоonChange
методу назначается checkForm
method, как показано в конструкторе. Наконец, вызывается метод thecheckForm
для обнаружения новых полей, пустое значение которых приведет к отключению кнопки отправки.
void newFields() { nameFields.addElement(StreamedValue<String>(); ageFields.addElement(StreamedValue<String>(); nameFields.value.last.onChange(checkForm); ageFields.value.last.onChange(checkForm); // This is used to force the checking of the form so that, adding // the new fields, it can reveal them as empty and sets the form // to not valid. checkForm(null); }
Часть 3 - интерфейс
DynamicFieldsPage
В методе build
класса State, связанного с этим StatefulWidget, возвращается Scaffold
с простым AppBar
и DynamicFieldsWidget
в его теле. В dispose
методе этого виджета вызывается dispose
метод экземпляра блока для закрытия всех открытых потоков.
class DynamicFieldsPage extends StatefulWidget { @override_DynamicFieldsPageState createState() => DynamicFieldsPageState(); } class _DynamicFieldsPageState extends State<DynamicFieldsPage> { @override void dispose() { bloc.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return SafeArea( child: Scaffold( appBar: AppBar( title: const Text(‘Dynamic fields validation’), ), body: DynamicFieldsWidget(), ), ); } }
DynamicFieldsWidget
Это виджет, содержащий поля. В методе build
он возвращает ListView
, так что можно добавить неопределенное количество полей. Внутри ListView есть виджет ValueBuilder
, который будет перестраивать виджеты в своем построителе каждый раз, когда поток генерирует новое событие. Внутри построителя есть виджет Column
, а его дочерние элементы создаются методом _buildFields
.
Кроме того, для кнопок используется виджет Row
: один для добавления новых полей, а другой - для отправки формы.
_buildFields (длина целого числа)
Этот метод принимает в качестве параметра длину списка полей имени и строит список полей.
В первой части очищается список TextEditingController
:
nameFieldsController.clear(); ageFieldsController.clear();
Затем с помощью цикла for
добавляется контроллер для каждого поля. Это будет использоваться для установки начального текста поля. Значения имен и возрастов берутся из StreamedValue
типа String, объявленного в классе BLoC.
for (int i = 0; i < length; i++) { final name = bloc.nameFields.value[i].value; final age = bloc.ageFields.value[i].value; nameFieldsController.add(TextEditingController(text: name)); ageFieldsController.add(TextEditingController(text: age)); }
Наконец, создается список из FieldsWidget
:
return List<Widget>.generate( length, (i) => FieldsWidget( index: i, nameController: nameFieldsController[i], ageController: ageFieldsController[i], ), );
FieldsWidget
Этот виджет принимает в качестве параметра индекс и два TextEditingController
и строит каждую пару полей имени / возраста, связанных с кнопкой для их удаления.
Индекс используется для перехода к StreamBuilder
каждого TextField
соответствующегоStreamedValue
.
StreamBuilder<String>( initialData: ‘ ‘, stream: bloc.nameFields.value[index].outStream, builder: (context, snapshot) { return Column( children: <Widget>[ Padding( padding: const EdgeInsets.symmetric( horizontal: 10, ), child: TextField( controller: nameController, style: const TextStyle( fontSize: 14, color: Colors.black, ), decoration: InputDecoration( labelText: ‘Name:’, hintText: ‘Insert a name…’, errorText: snapshot.error, ), onChanged: bloc.nameFields.value[index].inStream, ), ), ], ); }),
Давайте проанализируем этот фрагмент в его наиболее важных частях:
Поток:
В этом случае с помощью параметра index он передается в параметр потока StreamBuilder
единственного элемента StreamedList
nameFields (список StreamedValue<String>
) для создания текущего поля имени.
stream: bloc.nameFields.value[index].outStream,
- nameFields - это
StreamedList
ofStreamedValue<String>
- nameFields.value - это само
List
, а nameFields.value [индекс] - это одиночныйStreamedValue<String>
. - outStream - получатель потока одиночного
StreamedValue<String>
onChanged:
В параметр onChanged передается установщик inStream
того же StreamedValue<String>
, чтобы отправлять в поток каждое изменение в этом поле.
onChanged: bloc.nameFields.value[index].inStream,
Заключение
Это всего лишь метод проверки динамически создаваемых полей с потоками и шаблоном BLoC довольно простым способом. Как всегда, не стесняйтесь комментировать любые предложения, советы и т. Д. До встречи в следующей статье :-) А пока вы можете найти исходный код этого примера в этом репозитории GitHub.