Оглавление
  1. 1. Введение
  2. 2. Backend
  3. 2.1. Инфраструктура.
  4. 2.2. Доменное имя. SSL
  5. 2.3. Серверное приложение на Дарт
  6. ...
  7. 3. Web
  8. 3.1. FlutterWeb страница (мы находимся здесь)
  9. ...
  10. 4. Mobile
  11. ...


Подготовка


В прошлый раз мы закончили на том, что наш веб-сервер получил доменное имя и научился устанавливать безопасное соединение с клиентом. Однако нам пока совсем нечего показать нашему будущему пользователю. Хотя мы уже можем поделиться идеей стартапа и сообщить дату релиза MVP. Для такой задачи подойдёт информационная web-страница. Напишем её на Dart с использованием фреймворка FlutterWeb. Все наши клиентские приложения сервиса станут расширением именно этой страницы. Постараемся вести разработку максимально адаптивно и структурировано, чтобы развитие и сборки под нужные платформы (web-android-iOS) стали просто рутиной.



Начнём с установки Flutter:

  • Установим git
  • Склонируем репозиторий с beta версией Flutter командой
    git clone https://github.com/flutter/flutter.git -b beta
  • Для запуска команд flutter из командной строки необходимо указать в операционной системе путь до его исполняемых файлов. Откроем переменные ОС, для этого начнём вводить «изменение переменных среды текущего пользователя» в строке поиска



    В окне выберем переменную Path и нажмём Изменить. В открывшемся списке создадим новую строку с адресом до исполняемых файлов flutter в файловой системе, например C:\flutter\bin
  • Установим расширение VScode для flutter
  • Перезапустим VScode (чтобы применились новые переменные ОС) и в терминале проверим состояние flutter командой

    flutter doctor



    здесь самое важное, что flutter установлен в beta версии (с поддержкой web разработки)
  • Теперь активируем веб разработку командой

    flutter config --enable-web

Создание нового проекта и запуск отладки


Создаём новый проект командой

flutter create <название проекта>

Сразу откроем его в VScode командой

code <название проекта>

Откроем файл main.dart в папке lib и запустим отладку командой F5:



Возможно при первом запуске отладки понадобится выбрать Chrome устройством для отладки:



Удалим содержимое файла main.dart. Добавим пустой метод main и корневой класс приложения, возвращающий в методе build() экземпляр MaterialApp:



Далее создадим следующий набор вложенных папок проекта:



Кратко опишем назначение каждой из них:

  • di — механизм для связи между компонентами приложения. Здесь будут создаваться и регистрироваться все необходимые сервисы, репозитории, сетевые клиенты, необходимые для работы приложения. Я буду использовать библиотеку GetIt
  • domain — data-объекты. Классы представления данных
  • res — цвета, строки, импорты путей к картинкам и шрифтам. Всё, что связано со статическими ресурсами
  • service — сервисы для работы с данными
  • ui — интерфейс
  • utils — вспомогательные классы

В файле pubspec.yaml добавим необходимые зависимости:



Подготовка к масштабированию элементов UI


Предполагается, что наша страница должна адаптироваться в зависимости от размеров экрана клиентского устройства и его расположения (портретный или ландшафтный режим).

Начнём с картинок заднего фона. Их подготовка не входит в тему статьи, поэтому просто оставлю здесь эти две ссылки:

  • Pixabay.com — хранилище контентных фотографий
  • Paint.net — графический редактор

Готовые картинки разместим в папке /assets/images, в файле pubspec.yaml добавим этот путь к ресурсам:



Я предпочитаю доступ к ресурсам в виде дерева c параметрами. В данном случае путь к картинке заднего фона заглушки:

images.background(bool isPortrait).stub

Для этого в папке res создадим файл images.dart с классами адресов картинок:



Для масштабирования размеров интерфейса и шрифтов мы подключили библиотеку ScreenUtil. Её функциональность сводится к двум вещам:

  • Регистрация «базового» размера экрана. Здесь необходимо задать ширину и высоту экрана, для которого ведётся основная верстка и необходимость масштабирования шрифтов.
  • Набор расширений, позволяющий для чисел (num) применить масштабирующий коэффициент. Например 100.w означает, что результатом этого выражения будет для экрана шириной 1920dp => 100dp, а для экрана iPhone8 с шириной 414dp => 100х(414/1920) = 21,6dp. То есть в пять раз меньше. Также предусмотрены расширения для параметра высоты и размера шрифтов.

Создадим файл /utils/screen_util_ext.dart и статический метод инициализации в нём:



Вызов метода инициализации масштабирования добавим в метод build() корневого виджета:



Расширим функциональность библиотеки масштабирования несколькими дополнительными расширениями в файле /utils/screen_util_ext.dart:







Инъекция зависимостей


Пришло время внедрить механизм создания и регистрации компонентов приложения с помощью библиотеки GetIt. В папке lib/DI/ создадим файл di_container.dart. В нём напишем статический метод getItInit() и инициализируем экземпляр контейнера GetIt. Зарегистрируем первый компонент — экземпляр класса Images:



Вызов метода инициализации добавим в main():



Доступ к компоненту Images будет выглядеть так:



Таким же образом зарегистрируем класс с ресурсами строками.

Страница-заглушка


Теперь в папке UI создадим файл stub.dart с классом страницы заглушки StubScreen, расширим базовый класс StatelessWidget и переопределим его абстрактный метод build(). Наша страница представляет собой картинку на заднем плане и два информационных блока перед ней, размещающихся в зависимости от ориентации экрана.







Репозитории и сервис


Для динамического отображения оставшегося до релиза времени необходимо:

  1. Получить с сервера настройки с датами начала разработки и релиза
  2. Создать поток событий изменения оставшегося времени
  3. Объединить эти данные, передав в выходной поток для отображения на UI

Опишем доменные объекты (POJO) для этих данных:





Репозитории для получения настроек и создания потока событий:





Сервис для логики событий:



Зарегистрируем эти компоненты в DI контейнере:



Виджет оставшегося времени


Оставшееся до релиза время можно представить как 4 числа: дни, часы, минуты, секунды. Представим эти параметры в виде перечисления:



Добавим функциональности параметрам с помощью расширения:



Виджет для отображения круговой шкалы, числа и подписи будет анимированным, для этого расширим класс StatefulWidget. Его особенность в том, что Element (построенное и отображаемое представление) соотносится не с самим виджетом, а с его состоянием (State). Состояние, в отличие от виджета мутабельно. То есть его поля могут быть изменены без полного пересоздания экземпляра.





Здесь необходимо уточнить, что такое Animation, AnimationController и TickerProviderStateMixin. Итак AnimationController — обёртка над простым параметром double value. Значение этого параметра меняется линейно в пределах от 0,0 до 1,0 (также его можно менять в обратную сторону или сбрасывать в 0,0). Однако для изменения этого параметра используется специальный объект TickerProviderStateMixin, который является обязательным параметром для AnimationController и сообщает ему, что графический движок готов построить новый кадр. Получив такой сигнал AnimationController рассчитывает сколько времени прошло от предыдущего кадра и вычисляет насколько нужно изменить значение своего value. Объекты Animation подписываются на AnimationController и содержат в себе некоторую функцию зависимости выходного значения от линейного (по времени) изменения значения AnimationController.

Метод инициализации состояния initState() вызывается один раз при создании:



При уничтожении состояния виджета вызывается метод dispose():





Представлением виджета будет стек (Stack), с помещёнными в него AnimatedBuilder для числа и шкалы:



Остаётся реализовать графический примитив в виде дуги:



Добавим 4 таких виджета на экран заглушки:



Сборка и релиз


Перед сборкой приложения необходимо заменить название и описание приложения в файлах ./web/index.html ./web/manifest.json и pubspec.yaml.

Останавливаем отладку и собираем релиз приложения командой

flutter build web

Готовое приложение находится в директории ./build/web/. Обратите внимание, что файлы .last_build_id и main.dart.js.map служебные и могут быть удалены.
Разместим приложение на сервере, подготовленном в предыдущей статье. Для этого достаточно скопировать содержимое директории ./build/web/ в /public/ нашего сервера:
scp -r ./* root@91.230.60.120:/public/


Результат

Исходный код github

Вопросы и комментарии приветствуются. Пообщаться с автором можно в Telegram канале.

Вместо заключения


Наше клиентское приложение уже готово получить первые данные с сервера — сведения о дате релиза. Для этого в четвертой статье мы создадим каркас серверного приложения и разместим его на сервере.