ru:https://highload.today/blogs/kak-sozdat-prilozhenie-na-flutter-web-i-obojti-podvodnye-kamni/ ua:https://highload.today/uk/blogs/kak-sozdat-prilozhenie-na-flutter-web-i-obojti-podvodnye-kamni/
logo
Mobile app      08/09/2021

Как создать приложение на Flutter Web и обойти подводные камни без вреда для проекта

Елена Бойко BLOG

Android & Flutter Developer в NIX

Технология Flutter быстро заняла почетную нишу на рынке разработки кроссплатформенных мобильных веб-приложений. Основное преимущество — единая база кода, которая позволяет одновременно разрабатывать приложения как на iOS, так и на Android, как для десктопных, так и для веб-приложений.

При этом технология помогает сэкономить самый драгоценный ресурс для каждого разработчика — время.

На сегодня Flutter Web уже вышел в стабильной версии, а это дает бóльшие гарантии как для разработчиков, так и для продукта. Обычно в продакшне мы не используем библиотеки и фреймворки в бете. А в случае с Flutter Web уже есть стабильная версия инструментария.

Flutter интуитивно понятен и довольно легок в освоении: и в этой статье я покажу это на практике. Мы выясним, как с его помощью всего за один день можно создать веб-приложение при уже имеющихся мобильных приложениях. Также выясним, какие подводные камни таит в себе Flutter Web и как их обойти без вреда для проекта.

С чего начать

Чтобы приступить к разработке веб-приложения на Flutter Web, нужно выполнить несколько команд: 

flutter config --enable-web // добавление поддержки веба в файл настроек
flutter create . //  добавление поддержки веба в текущем проекте
flutter build web // создание релизного билда

В этой статье детально разберем команду flutter build web, отвечающую за создание папки web, которую затем можно разместить на веб-сервере. Это основная команда для создания проекта. Также выясним, что из себя представляет папка web.

Структура Flutter Web 

Папка web, которая создается через команду flutter build web, имеет следующий вид:

Папка web

Папка web

Давайте заострим внимание на трех ключевых файлах и поймем, за что они отвечают: 

  • index.html — простая html-страница, к которой подключаются другие файлы;
  • flutter_service_worker.js — скрипт, который способен перехватывать и изменять команды по запросу ресурсов, а также выполнять кэширование данных;
  • Data Engineering.
    Курс для тих, хто хоче навести лад в архітектурі даних та опанувати ключові інструменти дата-інженера на практиці.
    Реєстрація на курс
  • main.dart.js — сердце нашего веб-приложения, основной файл, ответственный за работу приложений на Flutter Web.

Генерацию main.dart.js можно увидеть на примере компиляции его кода с языка Dart в JavaScript:

Генерация main.dart.js

Генерация main.dart.js

Любое приложение, созданное на Flutter Web, обрабатывается при помощи движка Flutter Web Engine. Он содержит библиотеки и API, которые преобразовывают код в более низкоуровневый (для работы с html, CSS и Canvas). Компилятор Dart2js преобразовывает код с Dart в JavaScript.

Изначально Dart разрабатывался именно как язык веб-разработки, поэтому его преобразование на JS довольно хорошо оптимизировано.

Подводные камни Flutter Web

При разработке веб-приложения нужно понимать, что не все библиотеки и плагины, реализованные во Flutter, поддерживаются на Flutter Web. Поэтому важно искать альтернативы или писать собственные реализации, необходимые для корректной работы приложения.

Отсюда следует правило:

Если вы подозреваете, что ваше мобильное приложение на Flutter может обзавестись своей веб-версией, в разработке следует выбирать те плагины, которые поддерживает Flutter Web. Со списком поддерживаемых плагинов можно ознакомиться на сайте, отфильтровав поиск по категории Web.

Дополнительный список официальных плагинов для работы с Firebase также есть на сайте. Здесь можно увидеть, какие именно платформы поддерживают плагины: мобильные, web или desktop.

Где хранить данные и как реализовать сохранение на Flutter Web

Во Flutter Web существует четыре варианта, где можно хранить данные: Cookies, Local Storage, Session Storage и IndexedDB. Давайте сравним их и выделим сильные и слабые стороны каждого.

Сравнение вариантов для хранения данных

Cookies Local Storage Session Storage IndexedDB
 

Тип данных

Пары ключ-значение, строковые Пары ключ-значение, строковые Пары ключ-значение, строковые Пары ключ-значение, разные типы данных
Отправка вместе с запросом Да Нет Нет Нет
Размер 4 Кб 5 Мб 5 Мб Зависит от браузера и свободного места на диске
Время хранения Expiry time / не ограничено Не ограничено До закрытия вкладки Не ограничено
 

 

Назначение

Персонализации и отслеживания действий пользователя Локальное хранилище небольших объемов данных между сессиями Локальное хранилище небольших объемов данных в пределах одной вкладки Локальное хранилище больших объемов данных между сессиями

Давайте разберем подробнее:

  • Cookies — небольшие фрагменты данных (весом 4 Кб), которые отправляются сервером и хранятся на устройстве веб-пользователя. По типу данных Cookies являются строковыми парами ключ-значение. Срок их хранения задается свойством expiry time или не ограничивается по времени.
  • Local Storage — свойства глобального объекта браузера window. Данные здесь, как и в Сookies, хранятся в виде строковых пар ключ-значение, а время их хранения не ограничено. Для каждого домена браузер создает свой Local Storage. Доступ к данным в происходит гораздо быстрее, чем в Cookies. 
  • Session Storage похож на Local Storage за исключением одного принципиального отличия — времени хранения данных. Session Storage существует только в рамках текущей вкладки браузера. Используется он намного реже, нежели Local Storage.
  • IndexedDB представляет собой встроенную базу данных с индексами. Не имеет срока хранения, поэтому отлично подходит как локальное хранилище больших объемов данных между сессиями.

Сохранение данных на Flutter Web может быть реализовано такими способами:

  • Ручная реализация — используя библиотеки dart:html, dart:js, dart:indexed_db.
  • Реализация при помощи готовых плагинов, поддерживаемых Flutter Web. Сегодня существует множество плагинов, которые охватывают типичные кейсы сохранения данных на вебе.

Отличия в сохранении данных на мобильной и веб-версиях Flutter

Разберем этот кейс на примере Shared Preferences. Наиболее подходящим способом хранения данных здесь является Local Storage. Сохранение легко реализовывается вручную посредством библиотеки dart:html:

import ‘dart:html’;
window.localStorage[‘selected_id’] = id;

Как видите, мы обращаемся к свойству localStorage объекта window и сохраняем туда необходимые нам значения. Сложность этого подхода в том, что нужно писать две различные версии кода — для мобильной и для веб-версии приложения.

Математика та статистика для Data Science.
Курс, на якому ви навчитеся проводити статистичний аналіз даних за допомогою Python та розвинете математичне мислення для розв'язання реальних завдань Data Science.
Більше про курс

Так было до лета 2020 года, пока на сайте не появился плагин Shared_Preferences. Он добавил поддержку веба и способность сохранять данные в Local Storage для браузера, в NSUserDefaults — для iOS, в Shared Preferences — для Android.

SharedPreferenced prefs = await SharedPreferences.getInstance();
prefs.setInt(“selected_id”, id);

Сохранение локальных баз данных во Flutter Web

В этом случае можно использовать Local Storage, IndexedDB или же прибегнуть к помощи библиотеки sql.js. Но наиболее удобным выбором здесь станет использование готового ORM-решения. Сегодня одним из самых лучших ORM-плагинов является moor. Его функционал охватывает практически все типичные кейсы работы с базами данных, за исключением некоторых (пока еще здесь не реализована поддержка сущностей embedded). 

Moor

Создание БД для мобильный и веб-версий происходит по-разному:

import ‘package:moor_flutter/moor_flutter.dart’;
MyDatabase createDb() {
        return MyDatabase(FlutterQueryExecutor.inDatabaseFolder(path: ‘db.sqlite’));
}

import ‘package:moor/moor_web.dart’;
MyDatabase createDb() {
        return MyDatabase(WebDatabase(‘db’));
}

Используются разные импорты (moor_flutter и moor_web). Именно здесь и таится первый подводный камень — невозможно одновременно импортировать данные из разных библиотек. Для веб-приложения нельзя использовать мобильную версию и наоборот. Чем обусловлено такое ограничение и как его обойти?

Дело в том, что во Flutter Web невозможен импорт библиотеки dart:io, которая используется для работы с файлами, сокетами и другими io-составляющими.

Но мы можем воспользоваться особой фичей языка Dart — условным импортом, который позволяет импортировать библиотеки при наличии заданного условия. 

Пример условного импорта библиотеки выглядит так:

import ‘src/stub_impl.dart’
    if (dart.library.io) ‘src/io_impl.dart’
    if (dart.library.html) ‘src/web_impl.dart’
    as some_lib;

Как видно из примера, импорт будет совершен при условии, что текущая версия приложения поддерживает библиотеки dart.library.io или dart.library.html соответственно.

Кэширование ресурсов в браузере и Assets

При работе на вебе часто применяют Assets. Они используются для обозначения файлов и объектов, необходимых для корректной работы веб-приложения.Во время использования веб-приложения пользователем, ресурсы будут кэшироваться в его браузере и при обновлении приложения могут отображаться некорректно. Это происходит, потому что в браузер загружаются варианты ресурсов из предыдущей версии приложения (до его обновления).

Ситуацию можно исправить несколькими способами:

  • Cache-busting — основная идея в том, чтобы добавлять к URL-адресу ресурса информацию, связанную с версией приложения, хеша содержимого или же временными метками. Вы можете модифицировать название файла или добавлять новые параметры (через «?»), благодаря чему ресурс будет восприниматься как новый, и браузер загрузит его обновленную версию.
  • Service worker — скрипт, который способен перехватывать запрос на ваши ресурсы и модифицировать их. В нем можно прописать стратегию, по которой будет реализовано кэширование.

 На практике оба подхода применяются одинаково успешно. 

Responsive UI 

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

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

Нужно самостоятельно проверить какое-либо условие (например, размер экрана), а затем — используя if и else, указать параметр, который нужно применить для выбранной ширины экрана:

@override
Widget build(BuildContext context) {
    final screenWidth = MediaQuery.of(context) .size.width;
        
    return (screenWidth < 500)
        ? NarrowWidget()
        : WideWidget();
}

Но как узнать, какие начальные данные нужно указать для if и else? Можно прибегнуть к помощи MediaQuery — особому виджету, содержащему информацию о размере и ориентации экрана.

Еще один способ — использование константы kIsWeb. Метод покажет, было ли приложение скомпилировано для работы в вебе или нет. Таким образом можно понять, где именно открыто приложение — на мобильном устройстве или в браузере. Также можно использовать плагины platform_detect и web_browser_detect.

ResponsiveWidget и альтернативные варианты написания UI для веб-приложений

Чтобы постоянно не прописывать if и else в местах, где необходимо получить разный дизайн экрана, можно использовать ResponsiveWidget:

class MainScreen extends StatelessWidget {

    @override
    Widget build(BuildContext context) {
        return ResponsiveWidget.of(context).resolve(
            xLarge: (_) => MainWebScreen(),
            mobile: (_) => MainMobileScreen(),
        )(context);
    }
}

Здесь виден основной экран приложения (MainScreen). В его методе build используется ResponsiveWidget, который затем передал в метод resolve варианты виджетов для разных типов экрана. В методе resolve реализована логика получения информации об экране. Затем resolve возвращает виджет, предназначенный для текущего размера экрана.

Существуют и альтернативные варианты создания UI для веб-приложений:

  • Bootstrap-подход: экран условно разбивается на сетку, а элементы интерфейса размещаются в одну или несколько колонок в зависимости от ширины экрана. Скачать плагин можно по ссылке.
  • Scaling-подход: элементы интерфейса увеличиваются или уменьшаются в зависимости от размера экрана. К этому варианту часто прибегают, когда дизайн для мобильного приложения и его веб-версии отличаются незначительно (размером или масштабируемостью элементов). Ознакомиться с плагином можно здесь.

Навигация во Flutter Web

Навигация во Flutter Web может быть реализована тремя способами:

  1. Использование вместо простых методов навигатора их Named-версий. Строчные параметры, которые вы туда передадите, и будут частью вашего URL после имени домена: Navigator.push -> Navigator.pushNamed.
  2. Использование плагинов, подходящих под ваши решения в реализации навигации. Здесь отметим fluro и flutter modular.
  3. Использование API: Navigator 2.0 — в нем содержится класс RouteInformationParser, который можно унаследовать и написать свой URL Parser для перехода на нужный скрин.

Описанные методы навигации на Flutter далеки от идеала, но они активно обновляются, так что советую следить за новинками и читать патчноуты. 

Нереализованные свойства и недостатки 

Среди распространенных «болячек», от которых до сих пор не излечился Flutter Web, часто выделяют следующие:

  • отсутствует hot reload  — реализован только hot restart; 
  • не все браузеры поддерживают стандартные шрифты;
  • отсутствует поддержка SEO;
  • довольно низкая производительность в мобильных браузерах.

Многие из этих фич планируется реализовать в ближайшее время, и, надеемся, к моменту выхода статьи этот список поредеет 🙂

Переход с Flutter на Flutter Web может показаться непривычным, но рассматривайте это как профессиональный челлендж. Тем более интерес, который IT-комьюнити проявляет к его развитию, подтверждает востребованность этой технологии в веб-разработке.

If you have found a spelling error, please, notify us by selecting that text and pressing Ctrl+Enter.

Курс QA.
Найпростіший шлях розпочати кар'єру в ІТ та ще й з гарантованим працевлаштуванням.
Приєднатися

Этот материал – не редакционный, это – личное мнение его автора. Редакция может не разделять это мнение.

Топ-5 самых популярных блогеров февраля

Всего просмотровВсего просмотров
229
#1
Всего просмотровВсего просмотров
229
Всего просмотровВсего просмотров
209
#2
Всего просмотровВсего просмотров
209
QA в CodeGeeks Solutions
Всего просмотровВсего просмотров
156
#3
Всего просмотровВсего просмотров
156
Senior Project Manager at Nemesis
Всего просмотровВсего просмотров
99
#4
Всего просмотровВсего просмотров
99
Software Architect at Devlify
Всего просмотровВсего просмотров
95
#5
Всего просмотровВсего просмотров
95
Рейтинг блогеров

Ваша жалоба отправлена модератору

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: