Суровые уроки, извлеченные из миграции нативного приложения iOS на Flutter

Мое первое занятие по разработке мобильных приложений началось около десяти лет назад. Я только что купил свой первый iPhone (3GS), и мне было любопытно создавать приложения для этого очаровательного маленького устройства. Приложение, которое я хотел разработать в то время, было (и остается) в основном электронной книгой о стероидах. Он содержит около 50 разделов информационного содержания и призван помочь своей аудитории в определенных чрезвычайных ситуациях. Таким образом, основная цель приложения - представить его контент таким образом, чтобы пользователь мог быстро и надежно найти то, что он ищет в стрессовой ситуации.

Изначально я начал проект как веб-приложение с использованием библиотеки jQTouch, заключенной в собственный контейнер PhoneGap, развернутый на iOS. Сразу после запуска приложение на неделю попало в список рекомендованных Apple приложений в категории Медицина, так что это выглядело так, как будто оно подействовало. Четыре года спустя я переписал приложение как собственное приложение на Swift, прежде чем я решил перенести его на Flutter в сентябре 2020 года. Вот что я узнал на последнем этапе пути.

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

Когда было принято решение создать наконец версию приложения для Android, возник очевидный вопрос: как это сделать? Родной порт Android? Кросс-платформенный? С Ionic и React или Vue.js? Или новый крутой парень в районе Флаттер? После некоторого исследования текущего состояния разработки мультиплатформенных приложений, похоже, было довольно много шумихи по поводу последнего. А из статей, которые были предложены алгоритмами Medium, у меня сложилось впечатление, что люди в целом вполне довольны проектом Google и что Flutter, похоже, достаточно повзрослел, чтобы стать серьезной альтернативой. Поэтому я решил попробовать.

Как ни удивительно, мой первоначальный опыт оказался таким же прекрасным, как и описанный в статьях. Вы не поверите, но без каких-либо предварительных знаний о Dart и принципах дизайна пользовательского интерфейса Flutter (исходящих от iOS Swift и Xcode Interface Designer) я запустил свое приложение на Android и iOS с Flutter в течение одной недели - ну, почти.

Когда дело дошло до тестирования, я случайно начал замечать очень странное поведение: в приложении я показываю счетчик во время загрузки главы электронной книги. Как только он загружен и отрисован в WebView, я заменяю счетчик отрисованным контентом. На самом деле все просто. Это срабатывало в 99% случаев, но в случайные моменты приложение зависало на счетчике - очевидно, что приложение, которое должно помочь вам в чрезвычайной ситуации, не подходит. Потребовалось около дня или двух, чтобы выяснить, что это было вызвано HttpServer Flutter, который вылетал на 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 в моем приложении.

Флаттер

  • cupertino_icons
  • HTTP
  • провайдер
  • shared_preferences
  • font_awesome_flutter
  • xml2json
  • дорожка
  • path_provider
  • мим
  • flutter_web_browser
  • webview_flutter
  • url_launcher
  • геолокатор
  • геокодирование
  • map_launcher
  • Wakelock
  • информация об устройстве
  • package_info
  • scrollable_positioned_list
  • in_app_review
  • Поделиться

МОЙ БОГ! Зачем мне столько пакетов во 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 кажется мне относительно полным (хотя Дарт еще предстоит пройти долгий путь, чтобы приблизиться к Swift).

Но способность Flutter к выполнению по-прежнему оставляет желать лучшего - особенно на iOS. Это связано с предвзятым отношением к Android? Или им просто не хватает опыта? Я не знаю. Но я точно знаю, что именно здесь Flutter терпит неудачу как кроссплатформенная среда разработки.

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

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

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

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