- 1. Введение
- 2. Backend
- 2.1. Инфраструктура.
- 2.2. Доменное имя. SSL
- 2.3. Серверное приложение на Дарт
- ...
- 3. Web
- 3.1. FlutterWeb страница (мы находимся здесь)
- ...
- 4. Mobile
- ...
Подготовка
В прошлый раз мы закончили на том, что наш веб-сервер получил доменное имя и научился устанавливать безопасное соединение с клиентом. Однако нам пока совсем нечего показать нашему будущему пользователю. Хотя мы уже можем поделиться идеей стартапа и сообщить дату релиза MVP. Для такой задачи подойдёт информационная web-страница. Напишем её на Dart с использованием фреймворка FlutterWeb. Все наши клиентские приложения сервиса станут расширением именно этой страницы. Постараемся вести разработку максимально адаптивно и структурировано, чтобы развитие и сборки под нужные платформы (web-android-iOS) стали просто рутиной.
![](https://habrastorage.org/webt/it/iy/ig/itiyigunmukjlz1zh-4d1exlhz8.png)
Начнём с установки 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:
![](https://habrastorage.org/webt/nq/zv/zn/nqzvzngnkeonetupnuhddy9sslk.png)
Возможно при первом запуске отладки понадобится выбрать Chrome устройством для отладки:
![](https://habrastorage.org/webt/za/lt/zp/zaltzpzbbgqbs37lxqj3k5dxlck.png)
Удалим содержимое файла main.dart. Добавим пустой метод main и корневой класс приложения, возвращающий в методе build() экземпляр MaterialApp:
![](https://habrastorage.org/webt/vs/4l/wh/vs4lwhgvlz4paxe4iuuuf4bgwzi.png)
Далее создадим следующий набор вложенных папок проекта:
![](https://habrastorage.org/webt/sv/wo/a_/svwoa_hifogjd_sgsrive7wxzxc.png)
Кратко опишем назначение каждой из них:
- di — механизм для связи между компонентами приложения. Здесь будут создаваться и регистрироваться все необходимые сервисы, репозитории, сетевые клиенты, необходимые для работы приложения. Я буду использовать библиотеку GetIt
- domain — data-объекты. Классы представления данных
- res — цвета, строки, импорты путей к картинкам и шрифтам. Всё, что связано со статическими ресурсами
- service — сервисы для работы с данными
- ui — интерфейс
- utils — вспомогательные классы
В файле pubspec.yaml добавим необходимые зависимости:
![](https://habrastorage.org/webt/yk/o5/fl/yko5fld5oui15ec_wdx0widgnnm.png)
Подготовка к масштабированию элементов UI
Предполагается, что наша страница должна адаптироваться в зависимости от размеров экрана клиентского устройства и его расположения (портретный или ландшафтный режим).
Начнём с картинок заднего фона. Их подготовка не входит в тему статьи, поэтому просто оставлю здесь эти две ссылки:
- Pixabay.com — хранилище контентных фотографий
- Paint.net — графический редактор
Готовые картинки разместим в папке /assets/images, в файле pubspec.yaml добавим этот путь к ресурсам:
![](https://habrastorage.org/webt/cc/ml/dq/ccmldqr_tjzdfdgunr34blj9dks.png)
Я предпочитаю доступ к ресурсам в виде дерева c параметрами. В данном случае путь к картинке заднего фона заглушки:
images.background(bool isPortrait).stub
Для этого в папке res создадим файл images.dart с классами адресов картинок:
![](https://habrastorage.org/webt/5d/vs/0z/5dvs0zkg09raew6km2xazy_rlqm.png)
Для масштабирования размеров интерфейса и шрифтов мы подключили библиотеку ScreenUtil. Её функциональность сводится к двум вещам:
- Регистрация «базового» размера экрана. Здесь необходимо задать ширину и высоту экрана, для которого ведётся основная верстка и необходимость масштабирования шрифтов.
- Набор расширений, позволяющий для чисел (num) применить масштабирующий коэффициент. Например 100.w означает, что результатом этого выражения будет для экрана шириной 1920dp => 100dp, а для экрана iPhone8 с шириной 414dp => 100х(414/1920) = 21,6dp. То есть в пять раз меньше. Также предусмотрены расширения для параметра высоты и размера шрифтов.
Создадим файл /utils/screen_util_ext.dart и статический метод инициализации в нём:
![](https://habrastorage.org/webt/na/hm/de/nahmdegvafla3e2ejpkll7qnoa4.png)
Вызов метода инициализации масштабирования добавим в метод build() корневого виджета:
![](https://habrastorage.org/webt/qw/jp/ds/qwjpdswijolh4tepbjc8tpsw0hc.png)
Расширим функциональность библиотеки масштабирования несколькими дополнительными расширениями в файле /utils/screen_util_ext.dart:
![](https://habrastorage.org/webt/g3/uu/ye/g3uuyeirfiujkgl82o29ldixbha.png)
![](https://habrastorage.org/webt/g_/w4/49/g_w449pht7b6owvvnpbqxvqhnoy.png)
![](https://habrastorage.org/webt/b6/qs/kp/b6qskpxnzo9b1k08zdfmlnt83rm.png)
Инъекция зависимостей
Пришло время внедрить механизм создания и регистрации компонентов приложения с помощью библиотеки GetIt. В папке lib/DI/ создадим файл di_container.dart. В нём напишем статический метод getItInit() и инициализируем экземпляр контейнера GetIt. Зарегистрируем первый компонент — экземпляр класса Images:
![](https://habrastorage.org/webt/bj/ou/dh/bjoudh3l9j8weysxeawdxkx7w-m.png)
Вызов метода инициализации добавим в main():
![](https://habrastorage.org/webt/_v/7i/hj/_v7ihjn8pjt6xhjl5s5fmxzaw-c.png)
Доступ к компоненту Images будет выглядеть так:
![](https://habrastorage.org/webt/mn/s9/rh/mns9rhkevuu_9px8ttark4xrk2a.png)
Таким же образом зарегистрируем класс с ресурсами строками.
Страница-заглушка
Теперь в папке UI создадим файл stub.dart с классом страницы заглушки StubScreen, расширим базовый класс StatelessWidget и переопределим его абстрактный метод build(). Наша страница представляет собой картинку на заднем плане и два информационных блока перед ней, размещающихся в зависимости от ориентации экрана.
![](https://habrastorage.org/webt/lv/up/jg/lvupjgfchivkiumjrwou0c0j6vy.png)
![](https://habrastorage.org/webt/cm/_m/uf/cm_muf1vrahgvwpfmpvcfwtuizk.png)
![](https://habrastorage.org/webt/x_/py/qt/x_pyqtxj_pei6fms4qpez5bsfie.png)
Репозитории и сервис
Для динамического отображения оставшегося до релиза времени необходимо:
- Получить с сервера настройки с датами начала разработки и релиза
- Создать поток событий изменения оставшегося времени
- Объединить эти данные, передав в выходной поток для отображения на UI
Опишем доменные объекты (POJO) для этих данных:
![](https://habrastorage.org/webt/8b/4o/ga/8b4oga_6edfouxqry6ct9tdwxke.png)
![](https://habrastorage.org/webt/-_/6p/m1/-_6pm1mrmw_nnynzygzznv3mfdi.png)
Репозитории для получения настроек и создания потока событий:
![](https://habrastorage.org/webt/ey/oh/9c/eyoh9ckrzmeshoqwo5app7rqptg.png)
![](https://habrastorage.org/webt/vg/zx/no/vgzxnob4igyjnwtcnfzij_kbx6a.png)
Сервис для логики событий:
![](https://habrastorage.org/webt/wp/lu/_u/wplu_ubbpnsw3_nrlmur9nhjmly.png)
Зарегистрируем эти компоненты в DI контейнере:
![](https://habrastorage.org/webt/xg/gh/yt/xgghytc_awq-goqaksauemwz3ve.png)
Виджет оставшегося времени
Оставшееся до релиза время можно представить как 4 числа: дни, часы, минуты, секунды. Представим эти параметры в виде перечисления:
![](https://habrastorage.org/webt/on/o1/hx/ono1hx01mmcm3ny0bgo6mg-0vto.png)
Добавим функциональности параметрам с помощью расширения:
![](https://habrastorage.org/webt/eg/jd/iv/egjdivypj-eqkn09yt6z5ke7zvy.png)
Виджет для отображения круговой шкалы, числа и подписи будет анимированным, для этого расширим класс StatefulWidget. Его особенность в том, что Element (построенное и отображаемое представление) соотносится не с самим виджетом, а с его состоянием (State). Состояние, в отличие от виджета мутабельно. То есть его поля могут быть изменены без полного пересоздания экземпляра.
![](https://habrastorage.org/webt/nt/3e/w9/nt3ew9jvkivjkjgmsb91jz1e1ee.png)
![](https://habrastorage.org/webt/ot/kf/8v/otkf8vzl-7fj4a31udidqtd4h38.png)
Здесь необходимо уточнить, что такое Animation, AnimationController и TickerProviderStateMixin. Итак AnimationController — обёртка над простым параметром double value. Значение этого параметра меняется линейно в пределах от 0,0 до 1,0 (также его можно менять в обратную сторону или сбрасывать в 0,0). Однако для изменения этого параметра используется специальный объект TickerProviderStateMixin, который является обязательным параметром для AnimationController и сообщает ему, что графический движок готов построить новый кадр. Получив такой сигнал AnimationController рассчитывает сколько времени прошло от предыдущего кадра и вычисляет насколько нужно изменить значение своего value. Объекты Animation подписываются на AnimationController и содержат в себе некоторую функцию зависимости выходного значения от линейного (по времени) изменения значения AnimationController.
Метод инициализации состояния initState() вызывается один раз при создании:
![](https://habrastorage.org/webt/je/dn/nh/jednnh73na1ypn1chtt1em6sxok.png)
При уничтожении состояния виджета вызывается метод dispose():
![](https://habrastorage.org/webt/uw/2e/tn/uw2etnrken2vbfxh5c7pjmlycek.png)
![](https://habrastorage.org/webt/qg/hm/iv/qghmivvidw9ybpgygijnn3qpyfe.png)
Представлением виджета будет стек (Stack), с помещёнными в него AnimatedBuilder для числа и шкалы:
![](https://habrastorage.org/webt/36/zc/4w/36zc4wyunuoz5s4turgsza8rkce.png)
Остаётся реализовать графический примитив в виде дуги:
![](https://habrastorage.org/webt/sq/n6/bz/sqn6bzzrve_crfefarejdxpo0vi.png)
Добавим 4 таких виджета на экран заглушки:
![](https://habrastorage.org/webt/0t/hj/tu/0thjtuyogcxoe77rtj5ux3b3a2c.png)
Сборка и релиз
Перед сборкой приложения необходимо заменить название и описание приложения в файлах ./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 канале.
Вместо заключения
Наше клиентское приложение уже готово получить первые данные с сервера — сведения о дате релиза. Для этого в четвертой статье мы создадим каркас серверного приложения и разместим его на сервере.
gudvinr
К третьей части из серии статей про веб-сервис на языке Dart наконец-то появился Dart, осталось совсем немного и увидим сервис.