Решение общих кроссплатформенных проблем при работе с Flutter

Опубликовано: 2022-03-10
Краткое резюме ↬ При использовании кросс-платформенных фреймворков люди могут забыть о нюансах каждой из платформ, на которых они хотят, чтобы их код работал. Эта статья направлена ​​на решение этой проблемы.

Я видел в Интернете много путаницы в отношении веб-разработки с помощью Flutter, и часто это, к сожалению, по неправильным причинам.

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

Это было по-настоящему кросс-платформенным в том смысле, что интерфейсы в любом случае были одинаковыми, потому что у вас был доступ только к тем интерфейсам, которые обычно доступны в Интернете.

Однако Flutter не таков: он изначально работает на каждой платформе, и это означает, что каждое приложение работает так же, как если бы оно было написано на Java/Kotlin или Objective-C/Swift на Android и iOS, в значительной степени. Вы должны это знать, потому что это означает, что вам нужно учитывать множество различий между этими очень разными платформами.

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

Еще после прыжка! Продолжить чтение ниже ↓

Пример 1: Хранение

Недавно я писал в своем блоге о необходимости другого подхода к хранению JWT в веб-приложениях по сравнению с мобильными приложениями.

Это связано с разным характером вариантов хранения платформ и необходимостью знать каждую из них и их собственные инструменты разработки.

Интернет

Когда вы пишете веб-приложение, у вас есть следующие варианты хранения:

  1. загрузка/загрузка файлов на/с диска, что требует взаимодействия с пользователем и поэтому подходит только для файлов, предназначенных для чтения или создания пользователем;
  2. использование файлов cookie, которые могут быть доступны или недоступны из JS (в зависимости от того, являются ли они httpOnly ) и автоматически отправляются вместе с запросами на заданный домен и сохраняются, когда они приходят как часть ответа;
  3. используя JS localStorage и sessionStorage , доступный любому JS на веб-сайте, но только из JS, который является частью страниц этого веб-сайта.

Мобильный

Совсем другая ситуация с мобильными приложениями. Варианты хранения следующие:

  1. локальные документы приложения или кэш-память, доступные этому приложению;
  2. другие пути локального хранения для файлов, созданных/читаемых пользователем;
  3. NSUserDefaults и SharedPreferences соответственно на iOS и Android для хранения ключей и значений;
  4. Keychain на iOS и KeyStore на Android для безопасного хранения соответственно любых данных и криптографических ключей.

Если вы этого не знаете, вы запутаете свои реализации, потому что вам нужно знать, какое решение для хранения вы на самом деле используете, и каковы его преимущества и недостатки.

Кроссплатформенные решения: первоначальный подход

При использовании пакета shared_preferences Flutter используется localStorage в Интернете, SharedPreferences в Android и NSUserDefaults в iOS. Это имеет совершенно разные последствия для вашего приложения, особенно если вы храните конфиденциальную информацию, такую ​​​​как токены сеанса: localStorage может быть прочитан клиентом, поэтому это проблема, если вы уязвимы для XSS. Несмотря на то, что мобильные приложения на самом деле не уязвимы для XSS, SharedPreferences и NSUserDefaults не являются безопасными методами хранения, поскольку они могут быть скомпрометированы на стороне клиента, поскольку они не являются безопасным хранилищем и не зашифрованы. Это потому, что они предназначены для пользовательских настроек, как упоминалось здесь в случае iOS и здесь в документации Android, когда речь идет о библиотеке безопасности, которая предназначена для предоставления оболочек для SharedPreferences специально для шифрования данных перед их сохранением.

Безопасное хранилище на мобильных устройствах

Единственными безопасными решениями для хранения на мобильных устройствах являются Keychain и KeyStore на iOS и Android соответственно, в то время как безопасного хранилища в Интернете нет .

Однако Keychain и KeyStore очень разные по своей природе: Keychain — это универсальное решение для хранения учетных данных, тогда как KeyStore используется для хранения (и может генерировать) криптографических ключей , либо симметричных ключей, либо открытых/закрытых ключей.

Это означает, что если, например, вам нужно сохранить токен сеанса, на iOS вы можете позволить ОС управлять частью шифрования и просто отправить свой токен в цепочку для Keychain , тогда как на Android это немного больше ручного опыта, потому что вам нужно для генерации (не жесткого кода, это плохо) ключа, используйте его для шифрования токена, сохраните зашифрованный токен в SharedPreferences и сохраните ключ в KeyStore .

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

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

Отсутствие безопасного хранилища в Интернете

Собственно, это и побудило меня написать этот пост. Я писал об использовании этого пакета для хранения JWT в мобильных приложениях, и людям нужна была его веб-версия , но, как я уже сказал, в Интернете нет безопасного хранилища . Его не существует.

Означает ли это, что ваш JWT должен быть открытым?

Нет, совсем нет. Вы можете использовать файлы cookie httpOnly , не так ли? Они недоступны для JS и отправляются только на ваш сервер. Проблема в том, что они всегда отправляются на ваш сервер, даже если один из ваших пользователей щелкает URL-адрес запроса GET на чужом веб-сайте, и этот запрос GET имеет побочные эффекты, которые вам или вашему пользователю не понравятся. Это на самом деле работает и для других типов запросов, просто это сложнее. Это называется подделкой межсайтовых запросов, и вам это не нужно. Это одна из угроз веб-безопасности, упомянутых в документации MDN Mozilla, где вы можете найти более полное объяснение.

Есть методы профилактики. На самом деле наиболее распространенным является наличие двух токенов: один из них попадает к клиенту как файл cookie httpOnly , а другой — как часть ответа. Последний должен храниться в localStorage а не в файлах cookie, потому что мы не хотим, чтобы он автоматически отправлялся на сервер.

Решение обоих

Что, если у вас есть и мобильное приложение, и веб-приложение?

С этим можно справиться одним из двух способов:

  1. Используйте ту же серверную конечную точку, но вручную получайте и отправляйте файлы cookie с помощью HTTP-заголовков, связанных с файлами cookie;
  2. Создайте отдельную внутреннюю конечную точку, не являющуюся веб-сервером, которая генерирует токен, отличный от любого из токенов, используемых веб-приложением, а затем разрешите обычную авторизацию JWT, если клиент может предоставить токен только для мобильных устройств.

Запуск разного кода на разных платформах

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

Создание плагина Flutter

В частности, для решения проблемы с хранилищем вы можете сделать это с помощью пакета плагинов: плагины предоставляют общий интерфейс Dart и могут запускать разный код на разных платформах, включая собственный код Kotlin/Java или Swift/Objective-C для конкретных платформ. . Разработка пакетов и плагинов довольно сложна, но это объясняется во многих местах в Интернете и в других местах (например, в книгах по Flutter), включая официальную документацию по Flutter.

Например, для мобильных платформ уже существует подключаемый модуль безопасного хранилища, и это flutter_secure_storage , пример использования которого вы можете найти здесь, но, например, он не работает в Интернете.

С другой стороны, для простого хранилища пар "ключ-значение", которое также работает в Интернете, существует кросс-платформенный разработанный Google сторонний пакет подключаемых модулей под названием shared_preferences , в котором есть компонент для Интернета, называемый shared_preferences_web , который использует NSUserDefaults , SharedPreferences или localStorage в зависимости от платформы.

TargetPlatform на Flutter

После импорта package:flutter/foundation.dart вы можете сравнить Theme.of(context).platform со значениями:

  • TargetPlatform.android
  • TargetPlatform.iOS
  • TargetPlatform.linux
  • TargetPlatform.windows
  • TargetPlatform.macOS
  • TargetPlatform.fuchsia

и напишите свои функции так, чтобы для каждой платформы, которую вы хотите поддерживать, они выполняли соответствующие действия. Это будет особенно полезно для следующего примера различия платформ, а именно различий в том, как виджеты отображаются на разных платформах.

В частности, для этого варианта использования также существует довольно популярный плагин flutter_platform_widgets , который упрощает разработку виджетов с учетом платформы.

Пример 2: различия в отображении одного и того же виджета

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

Однако это требует осторожности и внимания и, по крайней мере, базовых знаний о платформах, которые вы хотите поддерживать, их фактических нативных API и всего такого. Пользователям React Native нужно уделять этому еще больше внимания, потому что этот фреймворк использует встроенные виджеты ОС, поэтому вам на самом деле нужно уделять еще больше внимания тому, как выглядит приложение, тщательно тестируя его на обеих платформах, не имея возможности переключаться между ними. iOS и виджет материалов на лету, как это возможно с Flutter.

Что меняется без вашего запроса

Некоторые аспекты пользовательского интерфейса вашего приложения автоматически изменяются при смене платформы. В этом разделе также упоминаются различия между Flutter и React Native в этом отношении.

Между Android и iOS (флаттер)

Flutter способен отображать виджеты Material на iOS (и виджеты Cupertino (iOS-подобные) на Android), но он НЕ делает то же самое на Android и iOS: тематика Material специально адаптируется к соглашениям каждой платформы. .

Например, навигационная анимация и переходы, а также шрифты по умолчанию отличаются, но они не сильно влияют на ваше приложение.

Что может повлиять на ваш выбор, когда речь идет об эстетике или UX, так это тот факт, что некоторые статические элементы также меняются. В частности, некоторые значки меняются между двумя платформами, заголовки панели приложений находятся посередине на iOS и слева на Android (слева от доступного пространства на случай, если есть кнопка «Назад» или кнопка для открытия ящика (объяснено здесь). в руководстве по дизайну материалов и также известном как меню-гамбургер). Вот как выглядит приложение Material с Drawer на Android:

изображение приложения для Android, показывающее, где появляется заголовок панели приложения в приложениях Flutter Android Material
Приложение Material, работающее на Android: заголовок AppBar находится слева от свободного места. (Большой превью)

А как то же самое, очень простое приложение Material выглядит на iOS:

изображение приложения iOS, показывающее, где появляется заголовок панели приложения в приложениях Flutter iOS Material
Приложение Material, работающее на iOS: заголовок AppBar находится посередине. (Большой превью)

Между мобильным устройством и Интернетом и с выемками на экране (Flutter)

В Интернете немного другая ситуация, как упоминалось также в этой потрясающей статье об адаптивной веб-разработке с помощью Flutter: в частности, в дополнение к необходимости оптимизировать для больших экранов и учитывать то, как люди ожидают навигации по вашему сайту. — чему и посвящена эта статья — вам придется побеспокоиться о том, что иногда виджеты размещаются за пределами окна браузера. Кроме того, некоторые телефоны имеют выемки в верхней части экрана или другие препятствия для правильного просмотра вашего приложения из-за каких-либо препятствий.

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

В React Native

React Native требует гораздо большего внимания и гораздо более глубоких знаний о каждой платформе, в дополнение к тому, что вам нужно запустить симулятор iOS, а также эмулятор Android, по крайней мере, чтобы иметь возможность протестировать свое приложение на обеих платформах: это не так. то же самое, и он преобразует свои элементы пользовательского интерфейса JavaScript в виджеты для конкретной платформы. Другими словами, ваши приложения React Native всегда будут выглядеть как iOS — с элементами пользовательского интерфейса Cupertino, как их иногда называют, — а ваши приложения Android всегда будут выглядеть как обычные Android-приложения Material Design, потому что они используют виджеты платформы.

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

Как обойти эту проблему

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

Точно так же, как вы не должны просто отправлять мобильное приложение в Интернет (как я писал в вышеупомянутом посте Smashing), вы не должны отправлять приложение, полное виджетов Купертино, пользователям Android, например, потому что это будет сбивать с толку. большая часть. С другой стороны, возможность фактически запустить приложение с виджетами, предназначенными для другой платформы, позволяет вам протестировать приложение и показать его людям в обеих версиях без необходимости использовать для этого два устройства.

Другая сторона: использование неправильных виджетов по правильным причинам

Но это также означает, что вы можете выполнять большую часть своей разработки Flutter на рабочей станции Linux или Windows, не жертвуя опытом ваших пользователей iOS, а затем просто создавать приложение для другой платформы и не беспокоиться о его тщательном тестировании.

Следующие шаги

Кроссплатформенные фреймворки — это прекрасно, но они перекладывают ответственность на вас, разработчика, за понимание того, как работает каждая платформа и как сделать так, чтобы ваше приложение адаптировалось и было приятным в использовании для ваших пользователей. Другими мелочами, которые следует учитывать, может быть, например, использование разных описаний того, что может быть по сути одним и тем же, если на разных платформах существуют разные соглашения.

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

Дополнительные ресурсы

  • Веб-сайт Flutter Gallery и приложение для Android, демонстрирующие использование виджетов Flutter, типичных для разных платформ, и их независимость от платформы.
  • Документация API Flutter на TargetPlatform
  • Документация Flutter по созданию пакетов и плагинов
  • Документация Flutter по адаптации платформы
  • Документация MDN по файлам cookie