[Разработка под Android, Dart, Flutter] Основы Flutter для начинающих (Часть VI)

Автор Сообщение
news_bot ®

Стаж: 6 лет 9 месяцев
Сообщений: 27286

Создавать темы news_bot ® написал(а)
05-Июн-2021 18:31

Когда вы создаете различные формы (например: регистрации или входа) на 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
===========

Похожие новости: Теги для поиска: #_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