Создайте динамический виджет Input, форму, которая может трансформироваться в форму входа и регистрации в зависимости от режима аутентификации, анимируйте переход между формой регистрации и формой входа, а также проверьте вводимые пользователем данные.
- Введение
- Создание формы аутентификации во Flutter
- Создание виджета динамической текстовой формы
- Валидация ввода во Flutter
- Создание валидатора электронной почты во Flutter
- Создание валидатора паролей во Flutter
- Создание валидатора подтверждения пароля во Flutter
- Домашнее задание по валидаторам
- Как создать форму регистрации и входа во Flutter?
- Виджет формы авторизации: Временный
- Экран аутентификации
- Подключение экрана аутентификации к маршрутизатору
- Временная навигация к AuthScreen
- Полноценный виджет формы аутентификации
- Инстанцируйте AuthValidators внутри класса _AuthFormWidgetState.
- Создайте контроллеры и узлы фокуса.
- Создание неясного текста bool
- Режим авторизации
- Инстанцирование и утилизация
- Обработчик видимости пароля
- Виджет закусочной
- Подготовка к списку виджетов в колонке
- Виджеты ввода
- Кнопка регистрации/входа в систему
- Переключение режима аутентификации
- Анимированный переход регистрации/сигнализации
- Резюме
- Показать поддержку
Введение
Здравствуйте и добро пожаловать в Khadka’s Coding Lounge. Это Нибеш из Khadka’s Coding Lounge. Прежде всего, отдельное спасибо всем последователям и подписчикам этой серии блогов. Итак, до сих пор в этой серии мы сделали Splash Screen, User Onboarding System, Global Theme и пользовательские виджеты, такие как панель приложений, нижняя навигационная панель и ящик для нашего приложения.
Сегодняшний блог — одна из самых важных частей этой серии, как вы можете понять из названия, потому что теперь, согласно нашему экрану потока пользователей, приведенному ниже, мы должны аутентифицировать пользователя.
Аутентификация — это базовый, но очень важный аспект любого приложения, независимо от платформы. Бессерверные/безголовые приложения в наши дни в тренде. Среди них Firebase от Google является одним из популярных, особенно для мобильных приложений. В этой главе цикла мы создадим пользовательский интерфейс аутентификации. Мы создадим виджет динамической формы ввода для большей эффективности. Мы также напишем несколько валидаций для ввода и анимируем переходы формы регистрации <->.
Вы можете найти код до этого момента здесь.
Создание формы аутентификации во Flutter
Мы создадим простую форму. Для регистрации у нас будет четыре входа: email, имя пользователя, пароль и подтверждение пароля, в то время как форма входа использует только два входа: email и пароль.
Давайте создадим соответствующие файлы и папки в нашем проекте.
# cursor on root folder
# create folders
mkdir lib/screens/auth lib/screens/auth/widgets lib/screens/auth/providers lib/screens/auth/utils
# Create files
touch lib/screens/auth/auth_screen.dart lib/screens/auth/providers/auth_provider.dart lib/screens/auth/widgets/text_from_widget.dart lib/screens/auth/widgets/auth_form_widget.dart lib/screens/auth/utils/auth_validators.dart lib/screens/auth/utils/auth_utils.dart
Создание виджета динамической текстовой формы
Прежде чем мы создадим наш виджет динамического поля текстовой формы, давайте подробно рассмотрим, насколько динамичным он будет.
Мы создадим наш динамический виджет текстовой формы в файле text_from_widget.dart.
import 'package:flutter/material.dart';
class DynamicInputWidget extends StatelessWidget {
const DynamicInputWidget(
{required this.controller,
required this.obscureText,
required this.focusNode,
required this.toggleObscureText,
required this.validator,
required this.prefIcon,
required this.labelText,
required this.textInputAction,
required this.isNonPasswordField,
Key? key})
: super(key: key);
// bool to check if the text field is for password or not
final bool isNonPasswordField;
// Controller for the text field
final TextEditingController controller;
// Functio to toggle Text obscuractio on password text field
final VoidCallback? toggleObscureText;
// to obscure text or not bool
final bool obscureText;
// FocusNode for input
final FocusNode focusNode;
// Validator function
final String? Function(String?)? validator;
// Prefix icon for input form
final Icon prefIcon;
// label for input form
final String labelText;
// The keyword action to display
final TextInputAction textInputAction;
@override
Widget build(BuildContext context) {
return TextFormField(
controller: controller,
decoration: InputDecoration(
// Input with border outlined
border: const OutlineInputBorder(
// Make border edge circular
borderRadius: BorderRadius.all(Radius.circular(10.0)),
),
label: Text(labelText),
prefixIcon: prefIcon,
suffixIcon: IconButton(
onPressed: toggleObscureText,
// If is non-password filed like emal the suffix icon will be null
icon: isNonPasswordField
? const Icon(null)
: obscureText
? const Icon(Icons.visibility)
: const Icon(Icons.visibility_off),
),
),
focusNode: focusNode,
textInputAction: textInputAction,
obscureText: obscureText,
validator: validator,
// onSaved: passwordVlidator,
);
}
}
Давайте пройдемся по нескольким важным полям нашего класса динамического виджета ввода.
// bool to check if the text field is for password or not
final bool isNonPasswordField; //# 1
// Function to toggle Text obscuraction on password text field
final VoidCallback? toggleObscureText; //# 2
// to obscure text or not bool
final bool obscureText;
// Validator function
final String? Function(String?)? validator; //# 3
- Мы проверяем, является ли тип ввода паролем или нет. Это нам понадобится, чтобы определить, нужно ли отображать значок суффикса, который переключает видимость пароля.
- Булево-непонятный текст изменит видимость пароля со звездочек на строки и наоборот.
- Validator — это функция валидатора свойств в TextFormField. Она возвращает null в случае валидности и пользовательскую строку в случае невалидности.
Валидация ввода во Flutter
Теперь, когда наш динамический ввод готов. Давайте напишем несколько валидаторов, прежде чем перейдем к созданию нашей формы. Мы создаем отдельный файл auth_validators.dart только для этой цели. Мы напишем три функции, которые будут отдельно проверять Email, пароль и подтверждение пароля для ввода пользователем.
Создание валидатора электронной почты во Flutter
class AuthValidators {
// Create error messages to send.
// #1
static const String emailErrMsg = "Invalid Email Address, Please provide a valid email.";
static const String passwordErrMsg = "Password must have at least 6 characters.";
static const String confirmPasswordErrMsg = "Two passwords don't match.";
// A simple email validator that checks presence and position of @
// #2
String? emailValidator(String? val) {
final String email = val as String;
// If length of email is <=3 then its invlaid
// #3
if (email.length <= 3) return emailErrMsg;
// Check if it has @
// # 4
final hasAtSymbol = email.contains('@');
// find position of @
// # 5
final indexOfAt = email.indexOf('@');
// Check numbers of @
// # 6
final numbersOfAt = "@".allMatches(email).length;
// Valid if has @
// # 7
if (!hasAtSymbol) return emailErrMsg;
// and if number of @ is only 1
// # 8
if (numbersOfAt != 1) return emailErrMsg;
//and if '@' is not the first or last character
// # 9
if (indexOfAt == 0 || indexOfAt == email.length - 1) return emailErrMsg;
// Else its valid
return null;
}
}
Внутри файла auth_validators.dart мы создаем класс AuthValidators.
- Затем мы создали три сообщения об ошибках, которые будут отправлены в качестве вывода соответствующей ошибки.
- Email Validator Функция, которая принимает входное значение из формы ввода.
- Email считается недействительным, если его длина <=3.
- (4&7) Проверяет наличие символа @ и возвращает сообщение об ошибке при его отсутствии.
- (5&9) Проверяет положение символа @. Если позиция находится в начале или в конце, это означает, что письмо недействительно.
- (6&8) Если количество символов @ больше или меньше единицы, значит, письмо недействительно.
Это был очень простой валидатор, в котором мы проверяли четыре вещи: если вход для email пустой, длина входа email меньше 4, позиция и количество @ во входе.
Напоминание: Не меняйте порядок операторов возврата. Это приведет к ошибке.
Создание валидатора паролей во Flutter
Мы продолжим со вторым валидатором для пароля. Мы будем проверять пароль, только если он не пустой и его длина больше 5. Итак, снова внутри класса AuthValidator создадим новую функцию passwordValidator.
// Password validator
String? passwordVlidator(String? val) {
final String password = val as String;
if (password.isEmpty || password.length <= 5) return passwordErrMsg;
return null;
}
Создание валидатора подтверждения пароля во Flutter
Во время регистрации будет два поля ввода пароля, второе из которых необходимо для подтверждения заданного пароля. Наш валидатор confirmPassword будет принимать два ввода: оригинальный пароль и второй пароль.
// Confirm password
String? confirmPasswordValidator(String? val, firstPasswordInpTxt) {
final String firstPassword = firstPasswordInpTxt;
final String secondPassword = val as String;
// If either of the password field is empty
// Or if thier length do not match then we don't need to compare their content
// #1
if (firstPassword.isEmpty ||
secondPassword.isEmpty ||
firstPassword.length != secondPassword.length) {
return confirmPasswordErrMsg;
}
// If two passwords do not match then send error message
// #2
if (firstPassword != secondPassword) return confirmPasswordErrMsg;
return null;
}
Для подтверждения пароля мы проверяем:
- Если один из двух паролей пуст или если их длина не совпадает друг с другом. Если да, то возвращаем сообщение об ошибке.
- Второе условие сравнивает содержимое двух паролей, если они не совпадают, то возвращает сообщение об ошибке.
Вот так наши три валидатора готовы к работе.
В целом класс AuthValidators выглядит следующим образом.
class AuthValidators {
// Create error messages to send.
static const String emailErrMsg =
"Invalid Email Address, Please provide a valid email.";
static const String passwordErrMsg =
"Password must have at least 6 characters.";
static const String confirmPasswordErrMsg = "Two passwords don't match.";
// A simple email validator that checks presence and position of @
String? emailValidator(String? val) {
final String email = val as String;
// If length of email is <=3 then its invlaid
if (email.length <= 3) return emailErrMsg;
// Check if it has @
final hasAtSymbol = email.contains('@');
// find position of @
final indexOfAt = email.indexOf('@');
// Check numbers of @
final numbersOfAt = "@".allMatches(email).length;
// Valid if has @
if (!hasAtSymbol) return emailErrMsg;
// and if number of @ is only 1
if (numbersOfAt != 1) return emailErrMsg;
//and if '@' is not first or last character
if (indexOfAt == 0 || indexOfAt == email.length - 1) return emailErrMsg;
// Else its valid
return null;
}
// Password validator
String? passwordVlidator(String? val) {
final String password = val as String;
if (password.isEmpty || password.length <= 5) return passwordErrMsg;
return null;
}
// Confirm password
String? confirmPasswordValidator(String? val, firstPasswordInpTxt) {
final String firstPassword = firstPasswordInpTxt;
final String secondPassword = val as String;
// If either of the password field is empty
// Or if thier length do not match then we don't need to compare their content
if (firstPassword.isEmpty ||
secondPassword.isEmpty ||
firstPassword.length != secondPassword.length) {
return confirmPasswordErrMsg;
}
// If two passwords do not match then send error message
if (firstPassword != secondPassword) return confirmPasswordErrMsg;
return null;
}
}
Домашнее задание по валидаторам
Вот вам кое-что для практики.
- xyz..abc@mail.com — это недопустимый адрес электронной почты, поскольку за символами (_, ., -) всегда должны следовать буквы или цифры. Можете ли вы написать валидатор для этой ошибки?
- На валидаторе пароля проверьте, что он должен содержать как цифры, так и буквы, чтобы быть действительным.
- Добавьте валидатор имени пользователя, проверьте, что оно не должно совпадать с адресом электронной почты. Кстати, имя пользователя является необязательным параметром.
Как создать форму регистрации и входа во Flutter?
Виджет формы авторизации: Временный
Теперь, когда мы сделали динамический ввод и валидатор, пришло время собрать их вместе, чтобы создать визуальный вид формы аутентификации. Мы создадим наш виджет формы в файле auth_form_widget.dart, который будет отображаться в файле auth_screen.dart.
auth_form_widget.dart
import 'package:flutter/material.dart';
class AuthFormWidget extends StatefulWidget {
const AuthFormWidget({Key? key}) : super(key: key);
@override
State<AuthFormWidget> createState() => _AuthFormWidgetState();
}
class _AuthFormWidgetState extends State<AuthFormWidget> {
// Define Form key
final _formKey = GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8),
child: Form(
key: _formKey,
child: TextFormField(),
),
);
}
}
В flutter класс Form может выступать в качестве контейнера для отображения полей TextFormFields (если их несколько). Он также требует FormState, который можно получить с помощью глобального ключа типа FormState, как мы только что сделали. Прежде чем виджет формы разрастется, давайте подключим его к auth_screen и отобразим на экране приложения. Мы изменим его позже, после подключения экрана аутентификации к маршрутизатору.
Экран аутентификации
auth_screen.dart
import 'package:flutter/material.dart';
import 'package:temple/globals/settings/router/utils/router_utils.dart';
import 'package:temple/screens/auth/widgets/auth_form_widget.dart';
class AuthScreen extends StatelessWidget {
const AuthScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(APP_PAGE.auth.routePageTitle)),
body:
// Safe area prevents safe gards widgets to go beyond device edges
SafeArea(
//===========//
// to dismiss keyword on tap outside use listener
child: Listener(
onPointerDown: (PointerDownEvent event) =>
FocusManager.instance.primaryFocus?.unfocus(),
//===========//
child: SingleChildScrollView(
child: SizedBox(
width: double.infinity,
child: Column(children: [
// Display a welcome user image
Padding(
padding: const EdgeInsets.all(8.0),
child: Image.asset(
'assets/AuthScreen/WelcomeScreenImage_landscape_2.png',
fit: BoxFit.fill,
),
),
const AuthFormWidget()
]),
),
),
),
),
);
}
}
Вот auth_screen.dart, это все, в дальнейшем мы не будем вносить никаких изменений.
Поскольку приложение доступно только зарегистрированным пользователям, экран аутентификации будет первым экраном, на который попадет наш пользователь после входа в приложение. Поэтому нам не нужно отображать user_drawer или нижнюю панель навигации.
Отключение активной клавиатуры при нажатии на нее снаружи является важной характеристикой. Класс Listener реагирует на события жестов, такие как щелчок мыши, касание и т.д. Вместе с FocusManager мы можем отслеживать дерево узлов фокуса и расфокусировать активную клавиатуру. Вы можете задаться вопросом, почему я использую его на auth_screen в целом, а не на auth_form_widget. Это потому, что события детектора жестов должны охватывать всю видимую область, которой в данном случае является SingleChildScrollView.
Далее — секция изображения, если вы используете исходный код из репозитория GitHub, изображение уже должно быть в папке AuthScreen внутри assets. Давайте добавим путь в файл pubspec.
assets:
# Splash Screens
- assets/splash/om_splash.png
- assets/splash/om_lotus_splash.png
- assets/splash/om_lotus_splash_1152x1152.png
# Onboard Screens
- assets/onboard/FindTemples.png
- assets/onboard/FindVenues.png
// New line
# Auth Screens
- assets/AuthScreen/WelcomeScreenImage_landscape_2.png
Пришло время добавить auth_screen в app_router.dart
Подключение экрана аутентификации к маршрутизатору
app_router.dart
routes: [
GoRoute(
path: APP_PAGE.home.routePath,
name: APP_PAGE.home.routeName,
builder: (context, state) => const Home(),
),
// Add the onboard Screen
GoRoute(
path: APP_PAGE.onboard.routePath,
name: APP_PAGE.onboard.routeName,
builder: (context, state) => const OnBoardScreen()),
// New line from here
// Add Auth Screen on Go Router
GoRoute(
path: APP_PAGE.auth.routePath,
name: APP_PAGE.auth.routeName,
builder: (context, state) => const AuthScreen()),
],
Временная навигация к AuthScreen
Нам еще предстоит написать бэкенд/бизнес-логику в этом блоге. Но нам нужно протестировать пользовательский интерфейс. Поэтому давайте создадим временную ссылку в нашем файле user_drawer.dart, которая приведет нас к экрану auth_screen.
user_drawer.dart
...............
actions: [
ListTile(
leading: Icon(
Icons.person_outline_rounded,
color: Theme.of(context).colorScheme.secondary,
),
title: const Text('User Profile'),
onTap: () {
print("User Profile Button Pressed");
}),
// ============================//
// A temporarry link to auth screen
ListTile(
leading: Icon(
Icons.login,
color: Theme.of(context).colorScheme.secondary,
),
title: const Text('Register/Login'),
onTap: () => GoRouter.of(context).goNamed(APP_PAGE.auth.routeName)),
// ============================//
ListTile(
leading: Icon(
Icons.logout,
color: Theme.of(context).colorScheme.secondary,
),
title: const Text('Logout'),
onTap: () {
print("Log Out Button Pressed");
}),
],
.....
Сохраните все это и запустите приложение, перейдите к auth_screen из ящика пользователя (нажмите иконку человека для ящика), и вы увидите auth screen. Теперь, когда мы видим экран аутентификации, давайте создадим полноценную форму аутентификации.
Полноценный виджет формы аутентификации
Коды для файла auth_form_widget.dart очень длинные. Поэтому давайте сначала рассмотрим его по частям.
Инстанцируйте AuthValidators внутри класса _AuthFormWidgetState.
// Instantiate validator
final AuthValidators authValidator = AuthValidators();
Создайте контроллеры и узлы фокуса.
// controllers
late TextEditingController emailController;
late TextEditingController usernameController;
late TextEditingController passwordController;
late TextEditingController confirmPasswordController;
// create focus nodes
late FocusNode emailFocusNode;
late FocusNode usernameFocusNode;
late FocusNode passwordFocusNode;
late FocusNode confirmPasswordFocusNode;
Создание неясного текста bool
// to obscure text default value is false
bool obscureText = true;
Режим авторизации
Вместо того чтобы создавать два отдельных экрана для регистрации и входа, мы просто будем переключать несколько отображаемых входов, включая и выключая их. Для этого создадим булево значение для управления authMode.
// This will require toggling between register and signin mode
bool registerAuthMode = false;
Инстанцирование и утилизация
Инстанцируйте все контроллеры редактирования текста и узлы фокуса в функции initState. Аналогично, все они также должны быть утилизированы, поэтому давайте сделаем это с помощью метода dispose.
@override
void initState() {
super.initState();
emailController = TextEditingController();
usernameController = TextEditingController();
passwordController = TextEditingController();
confirmPasswordController = TextEditingController();
emailFocusNode = FocusNode();
usernameFocusNode = FocusNode();
passwordFocusNode = FocusNode();
confirmPasswordFocusNode = FocusNode();
}
@override
void dispose() {
super.dispose();
emailController.dispose();
usernameController.dispose();
passwordController.dispose();
confirmPasswordController.dispose();
emailFocusNode.dispose();
usernameFocusNode.dispose();
passwordFocusNode.dispose();
confirmPasswordFocusNode.dispose();
}
Обработчик видимости пароля
Создайте функцию, которая будет переключать видимость пароля при нажатии на соответствующую иконку.
void toggleObscureText() {
setState(() {
obscureText = !obscureText;
});
}
Виджет закусочной
Давайте создадим закусочную, которая будет выводить информацию о различных обстоятельствах.
// Create a scaffold messanger
SnackBar msgPopUp(msg) {
return SnackBar(
content: Text(
msg,
textAlign: TextAlign.center,
));
}
Подготовка к списку виджетов в колонке
Давайте изменим дочернюю форму Form, которую мы возвращаем, на колонку.
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8),
child: Form(
key: _formKey,
child: Column(children: [],)
),
);
}
Виджеты ввода
Пришло время создать наши формы ввода email, имени пользователя, пароля и подтверждения пароля. Внутри дочерних элементов колонки для виджета from мы будем добавлять вводимые данные по одному.
Ввод электронной почты
// Email
DynamicInputWidget(
controller: emailController,
obscureText: false,
focusNode: emailFocusNode,
toggleObscureText: null,
validator: authValidator.emailValidator,
prefIcon: const Icon(Icons.mail),
labelText: "Enter Email Address",
textInputAction: TextInputAction.next,
isNonPasswordField: true,
),
const SizedBox(
height: 20,
),
Убедитесь, что импортировали виджет DynamicInput
Ввод имени пользователя
// Username
DynamicInputWidget(
controller: usernameController,
obscureText: false,
focusNode: usernameFocusNode,
toggleObscureText: null,
validator: null,
prefIcon: const Icon(Icons.person),
labelText: "Enter Username(Optional)",
textInputAction: TextInputAction.next,
isNonPasswordField: true,
),
const SizedBox(
height: 20,
),
Ввод пароля
// password
DynamicInputWidget(
controller: passwordController,
labelText: "Enter Password",
obscureText: obscureText,
focusNode: passwordFocusNode,
toggleObscureText: toggleObscureText,
validator: authValidator.passwordVlidator,
prefIcon: const Icon(Icons.password),
textInputAction: registerAuthMode
? TextInputAction.next
: TextInputAction.done,
isNonPasswordField: false,
),
const SizedBox(
height: 20,
),
Подтверждение ввода пароля
// confirm password
DynamicInputWidget(
controller: confirmPasswordController,
focusNode: confirmPasswordFocusNode,
isNonPasswordField: false,
labelText: "Confirm Password",
obscureText: obscureText,
prefIcon: const Icon(Icons.password),
textInputAction: TextInputAction.done,
toggleObscureText: toggleObscureText,
validator: (val) => authValidator.confirmPasswordValidator(
val, passwordController.text),
),
const SizedBox(
height: 20,
),
Кнопка регистрации/входа в систему
Позже нам также понадобится создать и переключить функции для регистрации и входа в систему, когда мы настроим бэкенд firebase. Пока что мы просто переключим тексты регистрации/входа на поднятой кнопке.
// Toggle register/singin button text. Later on we'll also need to toggle register or signin function
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () {},
child: const Text('Cancel'),
),
const SizedBox(
width: 20,
),
ElevatedButton(
onPressed: () {},
child: Text(registerAuthMode ? 'Register' : 'Sign In'),
style: ButtonStyle(
elevation: MaterialStateProperty.all(8.0),
),
),
],
),
Переключение режима аутентификации
Управляйте режимом аутентификации с помощью функции setState().
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(registerAuthMode
? "Already Have an account?"
: "Don't have an account yet?"),
TextButton(
onPressed: () =>
setState(() => registerAuthMode = !registerAuthMode),
child: Text(registerAuthMode ? "Sign In" : "Regsiter"),
)
],
)
Анимированный переход регистрации/сигнализации
Если вы сохраните файл и запустите его. Вы, вероятно, заметите, что переключение не работает. Это потому, что мы еще не реализовали анимированный переход. Два виджета ввода: виджеты имени пользователя и подтверждения пароля должны быть скрыты во время процесса регистрации, но видны при регистрации. Поэтому мы будем использовать переключение видимости на основе значения переменной registerAuthMode, которую мы создали ранее. Для плавного перехода во время переключения мы будем использовать анимацию.
Класс AnimatedContainer может быть использован для анимации, а класс AnimatedOpacity — для плавного включения/выключения виджетов. С помощью их комбинации мы переключим ввод с анимированной непрозрачностью, в то время как анимированный контейнер будет плавно сжимать/заполнять пространство, занимаемое вводом.
Итак, давайте анимируем имя пользователя, следующий за ним виджет sized-box и виджет ввода confirm-password.
// Username
AnimatedContainer(
duration: const Duration(milliseconds: 500),
height: registerAuthMode ? 65 : 0,
child: AnimatedOpacity(
duration: const Duration(milliseconds: 500),
opacity: registerAuthMode ? 1 : 0,
child: DynamicInputWidget(
controller: usernameController,
obscureText: false,
focusNode: usernameFocusNode,
toggleObscureText: null,
validator: null,
prefIcon: const Icon(Icons.person),
labelText: "Enter Username(Optional)",
textInputAction: TextInputAction.next,
isNonPasswordField: true,
),
),
),
// We'll also need to fade in/out sizedbox
AnimatedOpacity(
duration: const Duration(milliseconds: 500),
opacity: registerAuthMode ? 1 : 0,
child: const SizedBox(
height: 20,
),
),
// Confirm password
AnimatedContainer(
duration: const Duration(milliseconds: 500),
height: registerAuthMode ? 65 : 0,
child: AnimatedOpacity(
duration: const Duration(milliseconds: 500),
opacity: registerAuthMode ? 1 : 0,
child: DynamicInputWidget(
controller: confirmPasswordController,
focusNode: confirmPasswordFocusNode,
isNonPasswordField: false,
labelText: "Confirm Password",
obscureText: obscureText,
prefIcon: const Icon(Icons.password),
textInputAction: TextInputAction.done,
toggleObscureText: toggleObscureText,
validator: (val) => authValidator.confirmPasswordValidator(
val, passwordController.text),
),
),
),
После этого вы сможете увидеть два ввода на экране аутентификации, поскольку режим входа в систему является нашим режимом по умолчанию. Теперь наш пользовательский интерфейс виджета формы готов. Не путайтесь, ниже вы можете найти весь auth_form_widget.dart целиком.
import 'package:flutter/material.dart';
import 'package:temple/screens/auth/utils/auth_validators.dart';
import 'package:temple/screens/auth/widgets/text_from_widget.dart';
class AuthFormWidget extends StatefulWidget {
const AuthFormWidget({Key? key}) : super(key: key);
@override
State<AuthFormWidget> createState() => _AuthFormWidgetState();
}
class _AuthFormWidgetState extends State<AuthFormWidget> {
// Define Form key
final _formKey = GlobalKey<FormState>();
// Instantiate validator
final AuthValidators authValidator = AuthValidators();
// controllers
late TextEditingController emailController;
late TextEditingController usernameController;
late TextEditingController passwordController;
late TextEditingController confirmPasswordController;
// create focus nodes
late FocusNode emailFocusNode;
late FocusNode usernameFocusNode;
late FocusNode passwordFocusNode;
late FocusNode confirmPasswordFocusNode;
// to obscure text default value is false
bool obscureText = true;
// This will require to toggle between register and sigin in mode
bool registerAuthMode = false;
// Instantiate all the *text editing controllers* and focus nodes on *initState* function
@override
void initState() {
super.initState();
emailController = TextEditingController();
usernameController = TextEditingController();
passwordController = TextEditingController();
confirmPasswordController = TextEditingController();
emailFocusNode = FocusNode();
usernameFocusNode = FocusNode();
passwordFocusNode = FocusNode();
confirmPasswordFocusNode = FocusNode();
}
// These all need to be disposed of once done so let's do that as well.
@override
void dispose() {
super.dispose();
emailController.dispose();
usernameController.dispose();
passwordController.dispose();
confirmPasswordController.dispose();
emailFocusNode.dispose();
usernameFocusNode.dispose();
passwordFocusNode.dispose();
confirmPasswordFocusNode.dispose();
}
// Create a function that'll toggle the password's visibility on the relevant icon tap.
void toggleObscureText() {
setState(() {
obscureText = !obscureText;
});
}
// Let's create a snack bar to pop info on various circumstances.
// Create a scaffold messanger
SnackBar msgPopUp(msg) {
return SnackBar(
content: Text(
msg,
textAlign: TextAlign.center,
));
}
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8),
child: Form(
key: _formKey,
child: Column(
children: [
// Email
DynamicInputWidget(
controller: emailController,
obscureText: false,
focusNode: emailFocusNode,
toggleObscureText: null,
validator: authValidator.emailValidator,
prefIcon: const Icon(Icons.mail),
labelText: "Enter Email Address",
textInputAction: TextInputAction.next,
isNonPasswordField: true,
),
const SizedBox(
height: 20,
),
// Username
AnimatedContainer(
duration: const Duration(milliseconds: 500),
height: registerAuthMode ? 65 : 0,
child: AnimatedOpacity(
duration: const Duration(milliseconds: 500),
opacity: registerAuthMode ? 1 : 0,
child: DynamicInputWidget(
controller: usernameController,
obscureText: false,
focusNode: usernameFocusNode,
toggleObscureText: null,
validator: null,
prefIcon: const Icon(Icons.person),
labelText: "Enter Username(Optional)",
textInputAction: TextInputAction.next,
isNonPasswordField: true,
),
),
),
AnimatedOpacity(
duration: const Duration(milliseconds: 500),
opacity: registerAuthMode ? 1 : 0,
child: const SizedBox(
height: 20,
),
),
DynamicInputWidget(
controller: passwordController,
labelText: "Enter Password",
obscureText: obscureText,
focusNode: passwordFocusNode,
toggleObscureText: toggleObscureText,
validator: authValidator.passwordVlidator,
prefIcon: const Icon(Icons.password),
textInputAction: registerAuthMode
? TextInputAction.next
: TextInputAction.done,
isNonPasswordField: false,
),
const SizedBox(
height: 20,
),
AnimatedContainer(
duration: const Duration(milliseconds: 500),
height: registerAuthMode ? 65 : 0,
child: AnimatedOpacity(
duration: const Duration(milliseconds: 500),
opacity: registerAuthMode ? 1 : 0,
child: DynamicInputWidget(
controller: confirmPasswordController,
focusNode: confirmPasswordFocusNode,
isNonPasswordField: false,
labelText: "Confirm Password",
obscureText: obscureText,
prefIcon: const Icon(Icons.password),
textInputAction: TextInputAction.done,
toggleObscureText: toggleObscureText,
validator: (val) => authValidator.confirmPasswordValidator(
val, passwordController.text),
),
),
),
const SizedBox(
height: 20,
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () {},
child: const Text('Cancel'),
),
const SizedBox(
width: 20,
),
ElevatedButton(
onPressed: () {},
child: Text(registerAuthMode ? 'Register' : 'Sign In'),
style: ButtonStyle(
elevation: MaterialStateProperty.all(8.0),
),
),
],
),
const SizedBox(
height: 20,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(registerAuthMode
? "Already Have an account?"
: "Don't have an account yet?"),
TextButton(
onPressed: () =>
setState(() => registerAuthMode = !registerAuthMode),
child: Text(registerAuthMode ? "Sign In" : "Regsiter"),
)
],
)
],
),
),
);
}
}
Наш валидатор все еще не работает, потому что мы все еще не разобрались с тем, что делать при отправке формы. Но давайте проверим результат нашей тяжелой работы.
Резюме
Давайте подведем итоги того, что мы сделали на данный момент.
- Создали виджет динамического ввода.
- Написали свои личные валидаторы для вводимых данных, которые мы будем принимать.
- Создали формы входа и регистрации на одном экране.
- Анимировали переход между регистрацией и аутентификацией Sign In.
Показать поддержку
На сегодня это все. Мы поработаем над настройкой Firebase Flutter в следующем разделе. Если у вас есть вопросы, то оставляйте их в разделе комментариев.
Спасибо за ваше время. Не забудьте поставить лайк и поделиться статьей. Надеюсь, вы также подпишитесь на публикацию, чтобы получать уведомления о следующих публикациях. Это Нибеш Кхадка из Khadka’s Coding Lounge. Мы являемся агентством фрилансеров, которое делает высококлассные веб-сайты и мобильные приложения.