[Разработка под Android, Dart, Flutter] Основы Flutter для начинающих (Часть VI)
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Когда вы создаете различные формы (например: регистрации или входа) на Flutter, вы не заморачиваетесь с кастомизацией компонентов, потому что вы можете изменить любое поле формы под свой стиль.Помимо кастомизации, Flutter предоставляет возможность обработки ошибок и валидации полей формы.И сегодня мы постараемся разобраться с этой темой на небольшом примере.Ну что ж, погнали!Наш план
- Часть 1 - введение в разработку, первое приложение, понятие состояния;
- Часть 2 - файл pubspec.yaml и использование flutter в командной строке;
- Часть 3 - BottomNavigationBar и Navigator;
- Часть 4 - MVC. Мы будем использовать именно этот паттерн, как один из самых простых;
- Часть 5 - http пакет. Создание Repository класса, первые запросы, вывод списка постов;
- Часть 6 (текущая статья) - работа с формами, текстовые поля и создание поста.
- Часть 7 - работа с картинками, вывод картинок в виде сетки, получение картинок из сети, добавление своих в приложение;
- Часть 8 - создание своей темы, добавление кастомных шрифтов и анимации;
- Часть 9 - немного о тестировании;
Создание формы: добавление постаДля начала добавим на нашу страницу HomePage кнопку по которой мы будем добавлять новый пост:
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Post List Page"),
),
body: _buildContent(),
// в первой части мы уже рассматривали FloatingActionButton
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
},
),
);
}
Далее создадим новую страницу в файле post_add_page.dart:
import 'package:flutter/material.dart';
class PostDetailPage extends StatefulWidget {
@override
_PostDetailPageState createState() => _PostDetailPageState();
}
class _PostDetailPageState extends State<PostDetailPage> {
// TextEditingController'ы позволят нам получить текст из полей формы
final TextEditingController titleController = TextEditingController();
final TextEditingController contentController = TextEditingController();
// _formKey пригодится нам для валидации
final _formKey = GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Post Add Page"),
actions: [
// пункт меню в AppBar
IconButton(
icon: Icon(Icons.check),
onPressed: () {
// сначала запускаем валидацию формы
if (_formKey.currentState!.validate()) {
// здесь мы будем делать запроc на сервер
}
},
)
],
),
body: Padding(
padding: EdgeInsets.all(15),
child: _buildContent(),
),
);
}
Widget _buildContent() {
// построение формы
return Form(
key: _formKey,
// у нас будет два поля
child: Column(
children: [
// поля для ввода заголовка
TextFormField(
// указываем для поля границу,
// иконку и подсказку (hint)
decoration: InputDecoration(
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.face),
hintText: "Заголовок"
),
// не забываем указать TextEditingController
controller: titleController,
// параметр validator - функция которая,
// должна возвращать null при успешной проверки
// или строку при неудачной
validator: (value) {
// здесь мы для наглядности добавили 2 проверки
if (value == null || value.isEmpty) {
return "Заголовок пустой";
}
if (value.length < 3) {
return "Заголовок должен быть не короче 3 символов";
}
return null;
},
),
// небольшой отступ между полями
SizedBox(height: 10),
// Expanded означает, что мы должны
// расширить наше поле на все доступное пространство
Expanded(
child: TextFormField(
// maxLines: null и expands: true
// указаны для расширения поля на все доступное пространство
maxLines: null,
expands: true,
textAlignVertical: TextAlignVertical.top,
decoration: InputDecoration(
border: OutlineInputBorder(),
hintText: "Содержание",
),
// не забываем указать TextEditingController
controller: contentController,
// также добавляем проверку поля
validator: (value) {
if (value == null || value.isEmpty) {
return "Содержание пустое";
}
return null;
},
),
)
],
),
);
}
}
Не забудьте добавить переход на страницу формы:
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
Navigator.push(context, MaterialPageRoute(
builder: (context) => PostDetailPage()
));
},
),
Запускаем и нажимаем на кнопку:
Вуаля! Форма работает.Небольшая заметкаУ новичков могут возникнуть проблемы даже с готовым кодом. И это не издевательство, такое бывает.Поэтому для 100%-ной работы коды постарайтесь использовать схожие версии Flutter и Dart с моими:
- Flutter 2.0.6
- Dart SDK version: 2.12.3
Также в комментах я обратил внимание на null safety. Это очень важно, я позабыл об этом и это мой косяк. Я уже добавил в приложение поддержку null safety. Вы наверно обратили внимание на восклицательный знак:
// ! указывает на то, что мы 100% уверены
// что currentState не содержит null значение
_formKey.currentState!.validate()
О null safety и о её поддержи в Dart можно сделать целый цикл статей, а возможно и написать целую книгу. Мы задерживаться не будем и переходим к созданию POST запроса.POST запрос для добавления данных на серверPOST, как уже было отмечено, является одним из HTTP методов и служит для добавления новых данных на сервер.Для начала добавим модель для нашего результата и изменим немного класс Post:
class Post {
// все поля являются private
// это сделано для инкапсуляции данных
final int? _userId;
final int? _id;
final String? _title;
final String? _body;
// создаем getters для наших полей
// дабы только мы могли читать их
int? get userId => _userId;
int? get id => _id;
String? get title => _title;
String? get body => _body;
// добавим новый конструктор для поста
Post(this._userId, this._id, this._title, this._body);
// toJson() превращает Post в строку JSON
String toJson() {
return json.encode({
"title": _title,
"content": _body
});
}
// Dart позволяет создавать конструкторы с разными именами
// В данном случае Post.fromJson(json) - это конструктор
// здесь мы принимаем объект поста и получаем его поля
// обратите внимание, что dynamic переменная
// может иметь разные типы: String, int, double и т.д.
Post.fromJson(Map<String, dynamic> json) :
this._userId = json["userId"],
this._id = json["id"],
this._title = json["title"],
this._body = json["body"];
}
// у нас будут только два состояния
abstract class PostAdd {}
// успешное добавление
class PostAddSuccess extends PostAdd {}
// ошибка
class PostAddFailure extends PostAdd {}
Затем создадим новый метод в нашем Repository:
// добавление поста на сервер
Future<PostAdd> addPost(Post post) async {
final url = Uri.parse("$SERVER/posts");
// делаем POST запрос, в качестве тела
// указываем JSON строку нового поста
final response = await http.post(url, body: post.toJson());
// если пост был успешно добавлен
if (response.statusCode == 201) {
// говорим, что все ок
return PostAddSuccess();
} else {
// иначе ошибка
return PostAddFailure();
}
}
Далее добавим немного кода в PostController:
// добавление поста
// функция addPost будет принимать callback,
// через который мы будет получать результат
void addPost(Post post, void Function(PostAdd) callback) async {
try {
final result = await repo.addPost(post);
// сервер вернул результат
callback(result);
} catch (error) {
// произошла ошибка
callback(PostAddFailure());
}
}
Ну что ж пора нам вернуться к нашему представлению PostAddPage:
class PostDetailPage extends StatefulWidget {
@override
_PostDetailPageState createState() => _PostDetailPageState();
}
// не забываем поменять на StateMVC
class _PostDetailPageState extends StateMVC {
// _controller может быть null
PostController? _controller;
// получаем PostController
_PostDetailPageState() : super(PostController()) {
_controller = controller as PostController;
}
// TextEditingController'ы позволят нам получить текст из полей формы
final TextEditingController titleController = TextEditingController();
final TextEditingController contentController = TextEditingController();
// _formKey нужен для валидации формы
final _formKey = GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Post Add Page"),
actions: [
// пункт меню в AppBar
IconButton(
icon: Icon(Icons.check),
onPressed: () {
// сначала запускаем валидацию формы
if (_formKey.currentState!.validate()) {
// создаем пост
// получаем текст через TextEditingController'ы
final post = Post(
-1, -1, titleController.text, contentController.text
);
// добавляем пост
_controller!.addPost(post, (status) {
if (status is PostAddSuccess) {
// если все успешно то возвращаемя
// на предыдущую страницу и возвращаем
// результат
Navigator.pop(context, status);
} else {
// в противном случае сообщаем об ошибке
// SnackBar - всплывающее сообщение
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Произошла ошибка при добавлении поста"))
);
}
});
}
},
)
],
),
body: Padding(
padding: EdgeInsets.all(15),
child: _buildContent(),
),
);
}
Widget _buildContent() {
// построение формы
return Form(
key: _formKey,
// у нас будет два поля
child: Column(
children: [
// поля для ввода заголовка
TextFormField(
// указываем для поля границу,
// иконку и подсказку (hint)
decoration: InputDecoration(
border: OutlineInputBorder(),
prefixIcon: Icon(Icons.face),
hintText: "Заголовок"
),
// указываем TextEditingController
controller: titleController,
// параметр validator - функция которая,
// должна возвращать null при успешной проверки
// и строку при неудачной
validator: (value) {
// здесь мы для наглядности добавили 2 проверки
if (value == null || value.isEmpty) {
return "Заголовок пустой";
}
if (value.length < 3) {
return "Заголовок должен быть не короче 3 символов";
}
return null;
},
),
// небольшой отступ между полями
SizedBox(height: 10),
// Expanded означает, что мы должны
// расширить наше поле на все доступное пространство
Expanded(
child: TextFormField(
// maxLines: null и expands: true
// указаны для расширения поля
maxLines: null,
expands: true,
textAlignVertical: TextAlignVertical.top,
decoration: InputDecoration(
border: OutlineInputBorder(),
hintText: "Содержание",
),
// указываем TextEditingController
controller: contentController,
// также добавляем проверку поля
validator: (value) {
if (value == null || value.isEmpty) {
return "Содержание пустое";
}
return null;
},
),
)
],
),
);
}
}
Логика работы следующая:
- мы нажаем добавить новый пост
- открывается окно с формой, вводим данные
- если все ок, то возвращаемся на предыдущую страницу и сообщаем об этом иначе выводим сообщение об ошибке.
Заключительный момент, добавим обработку результата в PostListPage:
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
// then возвращает объект Future
// на который мы подписываемся и ждем результата
Navigator.push(context, MaterialPageRoute(
builder: (context) => PostDetailPage()
)).then((value) {
if (value is PostAddSuccess) {
// SnackBar - всплывающее сообщение
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Пост был успешно добавлен"))
);
}
});
},
),
Теперь тестируем:
К сожалению JSONPlaceholder на самом деле не добавляет пост и поэтому мы не сможем его увидеть среди прочих постов.ЗаключениеЯ надеюсь, что убедил вас в том, что работа с формами на Flutter очень проста и не требует почти никаких усилий.Большая часть кода - это создание POST запроса на сервер и обработка ошибок.Полезные ссылки
Всем хорошего кода)
===========
Источник:
habr.com
===========
Похожие новости:
- [Администрирование баз данных, Big Data] Путеводитель по базам данных в 2021 г (перевод)
- [Разработка под Android, Dart, Flutter] Основы Flutter для начинающих (Часть V)
- [Системное администрирование, PostgreSQL, Администрирование баз данных] Grafana дашборды для pgSCV
- [Unity] Пулинг объектов в Unity 2021+ (перевод)
- [Разработка мобильных приложений, Разработка под Android, Gradle] Проекты в Gradle 7: как не зависеть от зависимостей
- [Высокая производительность, PostgreSQL, Программирование, .NET, SQL] Как реляционная СУБД делает JOIN?
- [Разработка под Android, Dart, Flutter] Основы Flutter для начинающих (Часть IV)
- [Разработка под Android] Всё о PendingIntents (перевод)
- [Разработка под Android, Kotlin] Proto DataStore + AndroidX Preferences на Kotlin
- [Управление продуктом, Финансы в IT, Микросервисы] Fintech на практике: как Quadcode технологии для трейдинга и банкинга разрабатывает
Теги для поиска: #_razrabotka_pod_android (Разработка под Android), #_dart, #_flutter, #_forms, #_flutter, #_post, #_https, #_dart, #_mobile_development, #_android_development, #_razrabotka_pod_android (
Разработка под Android
), #_dart, #_flutter
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 12:38
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Когда вы создаете различные формы (например: регистрации или входа) на Flutter, вы не заморачиваетесь с кастомизацией компонентов, потому что вы можете изменить любое поле формы под свой стиль.Помимо кастомизации, Flutter предоставляет возможность обработки ошибок и валидации полей формы.И сегодня мы постараемся разобраться с этой темой на небольшом примере.Ну что ж, погнали!Наш план
@override
Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Post List Page"), ), body: _buildContent(), // в первой части мы уже рассматривали FloatingActionButton floatingActionButton: FloatingActionButton( child: Icon(Icons.add), onPressed: () { }, ), ); } import 'package:flutter/material.dart';
class PostDetailPage extends StatefulWidget { @override _PostDetailPageState createState() => _PostDetailPageState(); } class _PostDetailPageState extends State<PostDetailPage> { // TextEditingController'ы позволят нам получить текст из полей формы final TextEditingController titleController = TextEditingController(); final TextEditingController contentController = TextEditingController(); // _formKey пригодится нам для валидации final _formKey = GlobalKey<FormState>(); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Post Add Page"), actions: [ // пункт меню в AppBar IconButton( icon: Icon(Icons.check), onPressed: () { // сначала запускаем валидацию формы if (_formKey.currentState!.validate()) { // здесь мы будем делать запроc на сервер } }, ) ], ), body: Padding( padding: EdgeInsets.all(15), child: _buildContent(), ), ); } Widget _buildContent() { // построение формы return Form( key: _formKey, // у нас будет два поля child: Column( children: [ // поля для ввода заголовка TextFormField( // указываем для поля границу, // иконку и подсказку (hint) decoration: InputDecoration( border: OutlineInputBorder(), prefixIcon: Icon(Icons.face), hintText: "Заголовок" ), // не забываем указать TextEditingController controller: titleController, // параметр validator - функция которая, // должна возвращать null при успешной проверки // или строку при неудачной validator: (value) { // здесь мы для наглядности добавили 2 проверки if (value == null || value.isEmpty) { return "Заголовок пустой"; } if (value.length < 3) { return "Заголовок должен быть не короче 3 символов"; } return null; }, ), // небольшой отступ между полями SizedBox(height: 10), // Expanded означает, что мы должны // расширить наше поле на все доступное пространство Expanded( child: TextFormField( // maxLines: null и expands: true // указаны для расширения поля на все доступное пространство maxLines: null, expands: true, textAlignVertical: TextAlignVertical.top, decoration: InputDecoration( border: OutlineInputBorder(), hintText: "Содержание", ), // не забываем указать TextEditingController controller: contentController, // также добавляем проверку поля validator: (value) { if (value == null || value.isEmpty) { return "Содержание пустое"; } return null; }, ), ) ], ), ); } } floatingActionButton: FloatingActionButton(
child: Icon(Icons.add), onPressed: () { Navigator.push(context, MaterialPageRoute( builder: (context) => PostDetailPage() )); }, ), Вуаля! Форма работает.Небольшая заметкаУ новичков могут возникнуть проблемы даже с готовым кодом. И это не издевательство, такое бывает.Поэтому для 100%-ной работы коды постарайтесь использовать схожие версии Flutter и Dart с моими:
// ! указывает на то, что мы 100% уверены
// что currentState не содержит null значение _formKey.currentState!.validate() class Post {
// все поля являются private // это сделано для инкапсуляции данных final int? _userId; final int? _id; final String? _title; final String? _body; // создаем getters для наших полей // дабы только мы могли читать их int? get userId => _userId; int? get id => _id; String? get title => _title; String? get body => _body; // добавим новый конструктор для поста Post(this._userId, this._id, this._title, this._body); // toJson() превращает Post в строку JSON String toJson() { return json.encode({ "title": _title, "content": _body }); } // Dart позволяет создавать конструкторы с разными именами // В данном случае Post.fromJson(json) - это конструктор // здесь мы принимаем объект поста и получаем его поля // обратите внимание, что dynamic переменная // может иметь разные типы: String, int, double и т.д. Post.fromJson(Map<String, dynamic> json) : this._userId = json["userId"], this._id = json["id"], this._title = json["title"], this._body = json["body"]; } // у нас будут только два состояния abstract class PostAdd {} // успешное добавление class PostAddSuccess extends PostAdd {} // ошибка class PostAddFailure extends PostAdd {} // добавление поста на сервер
Future<PostAdd> addPost(Post post) async { final url = Uri.parse("$SERVER/posts"); // делаем POST запрос, в качестве тела // указываем JSON строку нового поста final response = await http.post(url, body: post.toJson()); // если пост был успешно добавлен if (response.statusCode == 201) { // говорим, что все ок return PostAddSuccess(); } else { // иначе ошибка return PostAddFailure(); } } // добавление поста
// функция addPost будет принимать callback, // через который мы будет получать результат void addPost(Post post, void Function(PostAdd) callback) async { try { final result = await repo.addPost(post); // сервер вернул результат callback(result); } catch (error) { // произошла ошибка callback(PostAddFailure()); } } class PostDetailPage extends StatefulWidget {
@override _PostDetailPageState createState() => _PostDetailPageState(); } // не забываем поменять на StateMVC class _PostDetailPageState extends StateMVC { // _controller может быть null PostController? _controller; // получаем PostController _PostDetailPageState() : super(PostController()) { _controller = controller as PostController; } // TextEditingController'ы позволят нам получить текст из полей формы final TextEditingController titleController = TextEditingController(); final TextEditingController contentController = TextEditingController(); // _formKey нужен для валидации формы final _formKey = GlobalKey<FormState>(); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text("Post Add Page"), actions: [ // пункт меню в AppBar IconButton( icon: Icon(Icons.check), onPressed: () { // сначала запускаем валидацию формы if (_formKey.currentState!.validate()) { // создаем пост // получаем текст через TextEditingController'ы final post = Post( -1, -1, titleController.text, contentController.text ); // добавляем пост _controller!.addPost(post, (status) { if (status is PostAddSuccess) { // если все успешно то возвращаемя // на предыдущую страницу и возвращаем // результат Navigator.pop(context, status); } else { // в противном случае сообщаем об ошибке // SnackBar - всплывающее сообщение ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Произошла ошибка при добавлении поста")) ); } }); } }, ) ], ), body: Padding( padding: EdgeInsets.all(15), child: _buildContent(), ), ); } Widget _buildContent() { // построение формы return Form( key: _formKey, // у нас будет два поля child: Column( children: [ // поля для ввода заголовка TextFormField( // указываем для поля границу, // иконку и подсказку (hint) decoration: InputDecoration( border: OutlineInputBorder(), prefixIcon: Icon(Icons.face), hintText: "Заголовок" ), // указываем TextEditingController controller: titleController, // параметр validator - функция которая, // должна возвращать null при успешной проверки // и строку при неудачной validator: (value) { // здесь мы для наглядности добавили 2 проверки if (value == null || value.isEmpty) { return "Заголовок пустой"; } if (value.length < 3) { return "Заголовок должен быть не короче 3 символов"; } return null; }, ), // небольшой отступ между полями SizedBox(height: 10), // Expanded означает, что мы должны // расширить наше поле на все доступное пространство Expanded( child: TextFormField( // maxLines: null и expands: true // указаны для расширения поля maxLines: null, expands: true, textAlignVertical: TextAlignVertical.top, decoration: InputDecoration( border: OutlineInputBorder(), hintText: "Содержание", ), // указываем TextEditingController controller: contentController, // также добавляем проверку поля validator: (value) { if (value == null || value.isEmpty) { return "Содержание пустое"; } return null; }, ), ) ], ), ); } }
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add), onPressed: () { // then возвращает объект Future // на который мы подписываемся и ждем результата Navigator.push(context, MaterialPageRoute( builder: (context) => PostDetailPage() )).then((value) { if (value is PostAddSuccess) { // SnackBar - всплывающее сообщение ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Пост был успешно добавлен")) ); } }); }, ), К сожалению JSONPlaceholder на самом деле не добавляет пост и поэтому мы не сможем его увидеть среди прочих постов.ЗаключениеЯ надеюсь, что убедил вас в том, что работа с формами на Flutter очень проста и не требует почти никаких усилий.Большая часть кода - это создание POST запроса на сервер и обработка ошибок.Полезные ссылки Всем хорошего кода) =========== Источник: habr.com =========== Похожие новости:
Разработка под Android ), #_dart, #_flutter |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 12:38
Часовой пояс: UTC + 5