Впервые я занялся разработкой мобильных приложений около десяти лет назад. Я только что купил свой первый iPhone (3GS), и мне было любопытно создать приложение для этого очаровательного маленького устройства. Приложение, которое я хотел разработать в то время, было (и остается) в основном электронной книгой на стероидах. Она содержит около 50 разделов и призвана помочь аудитории в определенных чрезвычайных ситуациях. Основная цель приложения — представить контент таким образом, чтобы пользователь мог быстро и надежно найти то, что он ищет в стрессовой ситуации.
Первоначально я начал проект как веб-приложение с использованием библиотеки jQTouch, заключенной в собственный контейнер PhoneGap, развернутый на iOS. Сразу после запуска приложение на неделю попало в список рекомендованных Apple приложений в категории «Медицина», так что казалось, что приложение действительно актуально. Четыре года спустя я переписал приложение как нативное приложение на Swift, прежде чем решил перенести его на Flutter в сентябре 2020 года. Вот что я узнал на последнем этапе пути.
Flutter: Святой Грааль кроссплатформенных приложений?
Когда было принято решение создать наконец версию приложения для Android, возник очевидный вопрос: как это сделать?Нативная версия для Android? Кроссплатформенный релиз? На Ionic и React или Vue.js? Или взять новую крутую платформу Flutter? После некоторого исследования текущего состояния разработки мультиплатформенных приложений я увидел довольно много шумихи по поводу последнего. А из статей, которые были предложены алгоритмами Medium, у меня сложилось впечатление, что люди в целом вполне довольны проектом Google и что Flutter, похоже, достаточно повзрослел, чтобы стать серьезной альтернативой остальным методам разработки. Поэтому я решил попробовать.
Как ни удивительно, мой первоначальный опыт оказался таким же прекрасным, как и описанный в статьях. Хотите верьте, хотите нет, но без каких-либо предварительных знаний о Dart и принципах дизайна пользовательского интерфейса Flutter (исходящих из Swift и Xcode Interface Designer) я запустил свое приложение на Android и iOS с Flutter в течение одной недели — ну, почти.
Когда дело дошло до тестирования, я случайно начал замечать очень странное поведение: в приложении я показываю спинер во время загрузки главы электронной книги. После того, как она загружена и отрисована в WebView, я заменяю счетчик отрисованным контентом. На самом деле все просто. Это срабатывало в 99% случаев, но в случайные моменты приложение зависало на спинере — очевидно, что для приложения, которое должно помочь вам в чрезвычайной ситуации, это не подходит. Потребовалось около дня или двух, чтобы выяснить, что это было вызвано HttpServer, который давал сбой на iOS, если пользователь на короткое время переключался на другое приложение, а затем снова на мое. Облом. Я отправил подробный отчет об ошибке — но в ближайшее время исправлений не будет.
Поскольку я не мог отправить свое приложение в таком состоянии, я начал искать альтернативные подходы. Вместо того, чтобы обрабатывать содержимое моей электронной книги через HttpServer, я решил скопировать содержимое из пакета приложения в каталог документов при первом запуске, а затем открыть HTML-код непосредственно в WebView, используя схему file://. После некоторых тестов и рефакторинга это казалось жизнеспособным решением — пока некоторые ссылки в электронной книге не перестали работать на реальном iOS-устройстве. Как ни странно, на Android и даже в симуляторе iOS все работало нормально. Оказалось, что я обнаружил еще одну ошибку, на этот раз в пакете Flutter WebView, которую я зарегистрировал и которая также была быстро обнаружена, воспроизведена и проверена командой. Но обе ошибки все еще существуют во Flutter на сегодняшний день (февраль 2021 года).
К тому времени, когда я работал над этой последней ошибкой, я потратил то же время, выслеживая и сообщая об ошибках во Flutter, что я потратил на изучение Dart и портирование моего приложения.
Ад зависимостей
Flutter HttpServer и WebView — две самые важные зависимости моего приложения. Без того или другого мое приложение просто не будет работать в этой среде. Исходя из моего опыта работы с iOS и CocoaPods, в наши дни я смотрю на каждую зависимость как на технический долг: вы заимствуете чей-то код или функциональность, и однажды вам, возможно, придется за это заплатить.
Любой фрагмент кода, который не принадлежит вам и от которого зависит ваше приложение, увеличивает технический долг вашего приложения.
Лично я считаю, что WebView и HttpServer являются основными частями технологического стека Flutter, поскольку они оба разрабатываются и поддерживаются непосредственно командой Flutter, а не какой-либо третьей стороной (избавлю вас от ужасных подробностей моих экспериментов со сторонним WebView для Flutter в качестве альтернативы).
Поэтому я был очень удивлен, что они, казалось бы, получили так мало внимания от команды Flutter. Я предполагаю, что либо нынешняя команда в Google просто недостаточно велика, чтобы реализовать такой проект, как Flutter (на сегодня есть 8 230 открытых тикетов), либо их приоритеты в другом. Но это вдохновило меня взглянуть на зависимости моего Flutter-приложения в целом и сравнить их с нативной Swift-версией моего приложения. Вот суть:
iOS (Swift)
- AEXML
- FontAwesome.swift
AEXML был необходим для синтаксического анализа некоторых файлов XHTML, поскольку собственный iOS SDK не предоставляет парсер DOM. Вторая библиотека позволила мне использовать популярные иконки 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
МОЙ БОГ! Зачем мне столько пакетов во Flutter? Что ж, делая полноценное приложение, а не электронную книгу, я добавил некоторые дополнительные функции и сервисы в свое приложение: онлайн-поиск профессионалов в текущем месте с использованием стороннего API, отображение некоторой информации о текущем местоположении пользователя с использованием геолокации и обратного геокодирование для помощи службам экстренной помощи и быстрый набор номеров телефонов экстренных служб. Для каждой функции требовалась определенная функция из нативного SDK платформы — например, геолокация, геокодирование, запуск звонка или предотвращение спящего режима, пока приложение открыто. Вдобавок к этому есть базовая функциональность, которую вы найдете в любом достойном приложении: время от времени запрашивать у пользователя обзор, разрешать делиться контентом с другими приложениями, открывать страницу по URL-адресу, открывать приложение Карты в определенном месте или получать доступ к некоторой информации о локальном устройстве и операционной системе.
В то время как iOS и Android предоставляют большую часть этих функций в своих родных SDK из коробки, кроссплатформенные решения должны предоставлять мост, пакет или подключаемый модуль для каждого из них. А в случае Flutter они должны быть реализованы на трех языках каждый: Dart, Swift/Objective-C и Java/Kotlin.
Ваше приложение Flutter будет зависеть от бесчисленных внешних библиотек, чтобы вы могли предлагать те же функции, что и нативное приложение. Таким образом, функциональность вашего приложения зависит от множества неизвестных сторон с разными целями, ограничениями по времени и мотивами к разработке. Технический долг вашего приложения будет увеличиваться с каждым используемым плагином.
Кроссплатформенность добавляет сложности
Если вы стремитесь к минимальному техническому долгу, вам нужно написать 100% кода приложения самостоятельно и использовать только собственные SDK для обеспечения доступа к базовому железу. Каждая сторонняя библиотека будет увеличивать ваш технический долг, поэтому, по крайней мере, выбирайте внимательно, если вы не можете реализовать функциональность самостоятельно.
Когда я начал тестировать свое родное приложение для iOS, я сначала обнаружил огромную утечку памяти. Откуда она взялась? Поняв концепцию циклов сильных ссылок и то, как их найти, я наконец обнаружил виновника: сторонняя XML-библиотека протекала при рекурсии. И поскольку я часто анализировал XHTML с ее помощью, мое приложение стало потреблять много памяти.
К счастью, с моими навыками Swift я смог найти ошибку в библиотеке, исправить ее в течение дня и даже отправить патч владельцу репозитория. Но я смог найти и исправить только потому, что библиотека и мое приложение были написаны на одном языке, а это был язык, с которым я был знаком.
Так что, если сторонняя библиотека имеет открытый исходный код и написана на том же языке, что и ваше приложение, вы вполне можете справиться с этим долгом. Но каждое кроссплатформенное решение добавляет вашему приложению несколько уровней сложности и вводит дополнительные точки отказа на каждом уровне.
Любой пакет Flutter может дать сбой в своем коде Dart, Swift/Objective-C или коде Java/Kotlin. Любое обновление Flutter, Dart, iOS или Android может сломать части этой иерархии, а вместе с ним и ваше приложение на одной или всех платформах.
Если вы не владеете всеми тремя языками и не владеете всеми поддерживаемыми платформами, вы, вероятно, не сможете или не захотите найти и исправить эти проблемы.
Примечание. В тот момент, когда я писал это, я получил второй запрос в службу поддержки от пользователя, запустившего мое приложение на iPhone 6 с iOS 12.4. Он сообщает, что главы не загружаются на его устройство. В моем симуляторе iOS с той же комбинацией оборудования и ОС все работает нормально. Серьезно: как мне отладить это, если у меня нет старого iPhone 6? И даже если мне удастся найти ошибку в iOS-части плагина Flutter WebView, я не разбираюсь в Objective-C и не хочу тратить больше времени на попытки исправить что-то, что IMHO должно просто работать — или иначе должно быть объявлено как альфа или бета!
На кого положиться?
Дополнительная сложность пакетов Flutter не была бы такой серьезной, если бы за проектом стояла сильная организация, которая чувствовала бы ответственность за обеспечение бриджей для всех основных функций нативного SDK в Dart. Но множество компонентов в этой критически важной части исключено из основного проекта Flutter и оставлено на волонтеров. Их приверженность и качество работы варьируются от высококлассной работы до потери интереса после версии 0.1. Если вы полагаетесь на пакет от преданного профессионала, это хорошо для вас. Если вы зависите от последнего, вам позже придется выплачивать свой долг.
Все, что вам действительно нужно от Flutter, это написать свое приложение в Dart и развернуть его на iOS и Android. Вас заставили поверить, что использование этого кроссплатформенного решения позволит вам сделать это, сэкономив при этом ваше время и деньги по сравнению с альтернативными подходами. Теоретически Flutter (и другие решения) могут это сделать. Или, если говорить словами Gartner: видение Flutter кажется полностью подходит для меня (хотя Dart еще предстоит пройти долгий путь, чтобы приблизиться к Swift).
Но работоспособность Flutter по-прежнему оставляет желать лучшего, особенно на iOS. Это связано с предвзятым отношением к Android? Или им просто не хватает опыта? Я не знаю. Но я точно знаю, что именно здесь Flutter терпит неудачу как кроссплатформенная среда разработки.
Если кроссплатформенное решение не может надежно предоставлять часто используемые функции приложения на всех поддерживаемых платформах, оно теряет свои преимущества по сравнению с нативными приложениями, и в конечном итоге теряет лояльность разработчиков.
Судя по тому немногому, что я вижу со стороны, я серьезно сомневаюсь, что Google удастся сделать разработку с Flutter таким же безупречным опытом, как разработка нативной версии. Если они действительно стремятся к этому, им нужно будет вложить в проект намного больше усилий и рабочей силы. Это опять же требует больших денег, твердой приверженности проекту и четкого видения будущего.
Может, мне просто не повезло, и единственные два неисправных пакета во вселенной Flutter оказались моими основными зависимостями. Но, читая открытые запросы на GitHub, я как-то сомневаюсь в этом. И глядя на технический долг, накопленный Flutter-версией моего приложения, я действительно молюсь за всех коллег-разработчиков, которые уже вскочили на поезд Flutter. Надеюсь, что Google приложит все усилия, вместо того, чтобы однажды потерять интерес и позволить проекту умереть.