11 апреля 2021

📱 Flutter, который не смог: провал кроссплатформенного решения

Frontend-разработчик в Foquz. https://www.cat-in-web.ru/
Печальная история о провале модного инструмента кроссплатформенной мобильной разработки или суровые уроки, извлеченные из миграции нативного приложения iOS на Flutter.
📱 Flutter, который не смог: провал кроссплатформенного решения

Впервые я погрузился в мир разработки мобильных приложений около десяти лет назад. Я только что купил свой первый iPhone (3GS), и мне было любопытно сделать что-нибудь для этого чудесного маленького устройства (статья Георга фон дер Ховена публикуется в переводе – прим. ред.).

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

Сначала я написал стандартное веб-приложение с использованием библиотеки jQTouch и завернул его в контейнер PhoneGap для iOS. После запуска мой проект очень быстро попал в список рекомендованных приложений Apple в медицинской категории. Кажется, мне удалось сделать что-то хорошее.

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

📱 Flutter, который не смог: провал кроссплатформенного решения

Flutter: Святой Грааль для кросс-платформенных приложений?

Когда я только принял решение наконец создать Android-версию проекта, встал логичный вопрос – как это сделать. Писать нативное приложение или использовать кросс-платформенные решения? Ionic c React'ом или Vue? Или, может быть, новый модный Flutter вокруг которого сейчас много хайпа?

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

Поразительно, но сначала все шло очень хорошо. Я ничего не знал о Dart или принципах проектирования пользовательского интерфейса Flutter (пришедших из iOS Swift и XCode Interface Designer), но почти за одну неделю смог запустить проект на Android и iOS.

Когда дело дошло до тестирования, я столкнулся с очень странным поведением. Во время загрузки главы электронной книги отображается спиннер. Как только данные загружены и визуализированы, я заменяю спиннер на WebView. Это очень просто, и в 99% случаев все было нормально. Но иногда все зависало на спиннере. Очевидно, это не то поведение, которое вы ожидаете от приложения для чрезвычайных ситуаций.

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

Так оставлять было нельзя, поэтому я начал искать альтернативные подходы. Я решил отказаться от запросов через HTTPServer, при первом запуске копировать содержимое книги в каталог документов и открывать HTML прямо в WebView используя протокол file://.

После нескольких тестов и рефакторинга это решение казалось вполне жизнеспособным. До тех пор, пока некоторые ссылки в книге не стали ломаться на физическом устройстве iOS. При этом на Android и в эмуляторе iOS все работало нормально. Оказывается, я нашел еще один баг – в этот раз в пакете Flutter WebView. Я отправил отчет, который быстро был подтвержден. Однако обе ошибки все еще не исправлены (по состоянию на февраль 2021 года).

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

Ад зависимостей

Модули HTTPServer и WebView – это две самые важные зависимости моего проекта. Без одного или другого мое приложение просто не будет работать. После некоторого опыта работы с iOS и CocoaPods я смотрю на каждую зависимость как на технический долг. Когда вы заимствуете чей-то код и полагаетесь на него, возможно, вам однажды придется заплатить за это.

Я полагаю, что WebView и HTTPServer – одни из основных частей стека технологий Flutter, поскольку они разрабатываются и поддерживаются непосредственно командой Flutter, а не сторонними программистами. (И я избавлю вас от ужасных подробностей о том, как я экспериментировал со сторонним WebView для Flutter).

Я был крайне удивлен, что эти модули получают так мало любви и внимания от разработчиков Flutter. Вероятно, нынешняя команда Google недостаточно велика, чтобы поддерживать и развивать этот проект (8200+ незакрытых ишьюс на Github), либо их приоритеты больше его не включают.

Учитывая эти соображения, давайте взглянем на зависимости моего приложения Flutter и нативной версии проекта на Swift.

Нативное iOS приложение (Swift)

  • AEXML – для анализа XHTML-файлов. Собственный iOS SDK не предоставляет DOM-парсер.
  • FontAwesome.swift – позволяет использовать иконки FontAwesome.

Версия на Flutter

  • cupertino_icons
  • HTTP
  • provider
  • shared_preferences
  • font_awesome_flutter
  • xml2json
  • path
  • path_provider
  • mime
  • flutter_web_browser
  • webview_flutter
  • url_launcher
  • geolocator
  • geocoding
  • map_launcher
  • wakelock
  • device_info
  • package_info
  • scrollable_positioned_list
  • in_app_review
  • share

Ничего себе! Для чего мне нужно так много пакетов?

Так как мой проект – это не просто электронная книга, я добавил в него некоторые дополнительные фичи и сервисы, например:

  • онлайн-поиск с использованием стороннего API;
  • отображение информации о текущем местоположении пользователя с использованием геолокации и обратного геокодирования для помощи аварийным службам;
  • быстрый набор экстренных номеров.

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

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

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

📱 Flutter, который не смог: провал кроссплатформенного решения

Кроссплатформенность добавляет сложности

Если вы хотите свести технический долг к минимуму, придется написать 100% кода вашего приложения самостоятельно и использовать только нативные SDK для доступа к возможностям устройства. Конечно это практически невозможно. Поэтому, по крайней мере, тщательно выбирайте сторонние библиотеки, которые вы добавляете к вашему техническому долгу.

Когда я начал тестировать свое нативное приложение для iOS, то обнаружил огромную утечку памяти. Откуда она взялась? Разобравшись в концепции фантастических циклов сохранения, я наконец обнаружил виновника – стороннюю XML-библиотеку с ее бесконечной рекурсией.

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

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

В каждом пакете Flutter может встретиться баг в коде Dart, или в коде Swift/Objective-C, или в коде Java/Kotlin. А то и в нескольких частях сразу. Его может сломать любое обновление Flutter, Dart, iOS или Android – а вместе с ним сломается и ваше приложение на одной или всех платформах. Если вы не владеет всеми тремя языками и всеми поддерживаемыми платформами, то, вероятно, не захотите искать и устранять проблемы самостоятельно.

Примечание
Во время написания этой статьи я получил сообщение от пользователя, запускающего мое приложение на iPhone 6 с iOS 12.4. Он сообщил, что книга не работает на этом устройстве. Но в моем эмуляторе с той же комбинацией ОС и оборудования все работает нормально – как же мне отлаживать баг, если у меня нет старого iPhone 6?

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

📱 Flutter, который не смог: провал кроссплатформенного решения

На кого вы хотите положиться?

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

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

Все, что вам реально нужно от Flutter, – это написать свое приложение на Dart и развернуть его на iOS и Android. И вы верите, что кросс-платформенное решение позволит вам это сделать, сэкономив время и деньги по сравнению с альтернативными решениями.

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

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

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

***

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

Источники

Комментарии

ВАКАНСИИ

Добавить вакансию
Разработчик C++
Москва, по итогам собеседования

ЛУЧШИЕ СТАТЬИ ПО ТЕМЕ