В поисках Святого Грааля или мысли о cross-platform

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

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

На сегодня существует несколько решений, которые позволяют уменьшить дублирование и делать кросс-платформенные решения, но ни одно из них пока не стало стандартом де-факто. Опытный разработчик может возразить, что стандарт – это утопия и этого никогда не будет, но существуют примеры решений, которым индустрия сказала единогласное "да": Linux, Git, Docker и прочие.

Какие cross-platform решения существуют сегодня?

  • C/C++ native libraries
  • QT (1995)
  • PhoneGap/Cordova (2009, 2011 Adobe)
  • React Native (2015, Facebook)
  • Xamarin (2011, 2016 Microsoft)
  • Flutter (2017)
  • Kotlin/Native, Kotlin/Multiplatform (2017) и другие (Ionic, Sencha, …)

С/C++

Наверное, самый старый из подходов, надежный, но явно не самый простой.

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

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

Dropbox, избравший подобный подход в 2013 году, в 2019 решил от него отказаться. Если кратко, то они пробовали общие вещи писать на C++, но столкнулись с такими проблемами:

  • оверхэд в разработке
  • отсутствие необходимого тулинга
  • проблемы с хайрингом и экспертизой

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

Судя по коду мобильных клиентов, Telegram тоже использует этот подход. Но на мой вкус их клиенты написаны очень специфически. В коде Android-клиента несколько раз замечал места, которые кажутся намеренно частично обфусцированными. Если так, то все нормально.

Продуктово Telegram очень крутые, но технически много вопросов. В их решениях почти все кричит о том, что они "гениальные математики". Вот, к примеру, весьма убедительная критика их протокола.

React Native

В этом подходе вы по сути пишете React-приложение. React Native запускается в фоновом потоке, интерпретирует ваш JavaScript-код и обеспечивает взаимодействие с устройством.

Airbnb в свое время была одной из первых компаний, кто стал серьезно использовать ReactNative в продакшн. Это было в 2016 году.

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

Среди известных приложений, целиком или частично написанных на React Native, можно отметить клиенты Instagram и Skype.

PhoneGap/Cordova

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

То есть на этого "белого боксера" я бы тоже не ставил.

Xamarin

Xamarin имеет в своем портфолио достаточно успешные приложения. Например, американский UPS.

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

Но многие отмечают сложность поддержи и постоянную борьбу с ветряными мельницами в софте на стыке Xamarin и нативных платформ. Однако если вы хорошо знакомы с C# и .Net инфраструктурой, то вполне можно попробовать использовать для прототипа или MVP.

Flutter

Разработанный Google фреймворк позволяет писать приложения на языке Dart, который рендерит UI не нативными средствами платформы, а своим способом, написанным на C++, подобно игровым движкам.

Технологию начинают адаптировать большие игроки (Yandex, Alibaba и другие), хотя инфраструктура, откровенно говоря, сыровата и все нужно реализовывать с нуля. Google продолжает активно развивать этот подход, довольно успешно вливая ресурсы в маркетинг.

Среди успешных кейсов Google Ads, Яндекс.Лавка, eBay Motors. Или вот Meduza не так давно сделала новое приложение целиком на Flutter.

Есть разные мнения на счет Flutter. Наш Lord & Saviour Джэйк Уортон на эту тему высказался так. А еще помню читал его мысли о том, что мимикрировать под каждую из платформ – дорога в никуда.

Моя экспертиза во Flutter заканчивается на запуске простых hello world'ов и demo.

Но так как эта платформа сейчас на подъеме, то я попросил помощи у эксперта в этой области @otopba1. Спонсором дальнейших твитов о Flutter является именно он, за что ему большое спасибо.

В каждой бочке затычка

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

flutter-01.png

Это конечно круто, но весь код обрастает условиями типа Platform.isAndroid. Часть методов из библиотек и фреймворка работают только на определенных платформах, а часть вовсе кидают исключения, если вызывать на не предназначенной платформе.

Проблема не в тебе. Проблема во мне

Flutter активно растет. Это круто. Но вместе с количеством новых фич появляется неимоверное количество багов.

https://github.com/flutter/flutter/issues

flutter-02.png

Постоянно вылезают баги совершенно разного рода, начиная от нативных крашей, заканчивая вечно поломанными шрифтами на iOS.

Мы писали, мы писали, наши пальчики устали

Пишешь на flutter более-менее крупное приложение? Будь готов контрибьютить во flutter-плагины. Это такой репозиторий с самыми основными библиотеками, вроде работы с пушами. Большая часть плагинов имеет версию 0.x.

Вам постоянно придется форкать и дописывать библиотеки. А если ваша библиотека работает с фичами платформы, будьте добры еще написать платформенный код, кроме dart.

https://github.com/flutter/plugins

flutter-03.png

Разговор глухого с немым

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

Команда flutter мастерски игнорирует пул-реквесты. Иногда создается впечатление, что команда гугл принимает только реквесты длиною не более 5 строк. Пул-реквесты висят годами, в комментариях уже появляются свои приколы.

https://github.com/flutter/plugins/pull/1721

flutter-04.png

Если инструмент работает хорошо, надо хвалить руки

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

Одним махом семерых побивахом

Во Flutter виджеты бывают иммутабельные (StatelessWidget) и мутабельные (StatefulWidget). Идея StatefulWidget в том, чтобы не перерисовывать лишний раз экран, если состояние не поменялось.

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

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

Язык – это дорожная карта культуры

Dart вроде типизированный язык, а вроде и нет. Компилятор не будет ругаться если вы забудете где-то сделать return или не укажите тип объекта. IDE вам тоже не всегда подскажет.

flutter-05.png

Двое из ларца одинаковых с лица

Как бы flutter не прикидывался нативным приложением, все равно физика, отображение системных элементов и прочее отличается от нативных.

Периодически возникает ощущение, что у вас в руках какая-то подделка, а не настоящее приложение. Команда flutter постоянно улучшают поведение фреймворка, но ощущение, что никогда не достигнут совпадения 1 в 1.

flutter-06.png

Чем бы дитя не тешилось

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

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

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

Плюсы

  • действительно cross-platform

  • быстро развивается

  • большое комьюнити

  • заливка кода на лету работает очень быстро

  • можно легко и быстро прототипировать, ui делается удобно

  • очень быстрые и удобные в использовании анимации

  • Dart супер простой язык

  • вместе с flutter идёт большой набор инструментов: для профилирования, инспектирования верстки и тп

  • не обязательно писать полностью приложение на flutter. Можно встроить кусок flutter в натив или наоборот

Kotlin Multiplatform

Kotlin MultiPlatform – это наиболее разумный подход к cross-platform.

Он лишен большинства недостатков других подходов, самые явные из которых:

  • не требует придумывать новый UI или компоненты системы
  • не нужно переходить на целиком новую инфраструктуру
  • возможен постепенный переход

При этом подходе UI остается нативным для платформы. Работа с компонентами системы, датчиками и прочим тоже нативная.

А все остальное при желании можно вытянуть в общие модули:

  • сущности
  • бизнес-логику
  • storage
  • networking

Это не так просто, как кажется. Чтобы построить такое приложение, надо довольно четко понимать как строить common library, какие вещи выносить в expect/actual, как осуществлять взаимодействие суб-компонент.

Собственно на этом и зарабатывает консалтинг-компания, евангелист KMP – Touchlab https://touchlab.co

Но, если вы хоть раз делали библиотеку/sdk, которая использовалась для разных клиентов (пусть и одной платформы), то вероятно больших проблем для вас это не составит.

Большим камнем преткновения был различающаяся для JVM и Native Memory Model. Из-за этого была принята суровая thread-confined frozen concurrency. Но не так давно команда Kotlin объявила об изменениях, и вскоре сделают по-другому.

О подробности нового подхода к Memory Management можно почитать тут

blog.jetbrains.com/kotlin/2020/07/kotlin-native-memory-management-roadmap/

medium.com/@kpgalligan/kotlin-native-concurrency-changes-bbb1a5147e6

Самый большой недостаток KMP – это то, что подход еще очень сырой и immature. Но уже сейчас Cash App от Square написана с таким подходом. Да и ряд других приложений: Space от JetBrains, VMWare и прочие.

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

Вы можете компилировать все это в JavaScript и запускать, как это сделал Jake Wharton в https://github.com/JakeWharton/SdkSearch. Но вы не можете использовать этот подход для встраивания в другие frontend-фреймворки. Точнее можно, но только для внутренних нужд и суровых админок, где огромный bundle не играет роли.

Мне бы хотелось скомпилировать common library с логикой и просто заимпортить в существующем Vue или React проекте, чтобы делать вызовы. Но сейчас это не так просто.

Вот тут немного говорят о том же.

Довольно стремительно развиваются различные библиотеки, чтобы сделать жизнь в KMP-инфраструктуре попроще: Ktor (http-client & server), Serizalization (с поддержкой JSON, Protobuf, CBOR), и другие.

Но больше всего мне нравится SqlDelight – библиотека для работы с базой. До встречи с ней Room мне казался прекрасным, но сейчас SqlDelight кажется более удобным, продуманным и feature-rich инструментом.

В качестве резюме

Священный Грааль пока не найден. Но многие очень сильно стараются, чтобы сделать хорошее решение в направлении cross-platform. Ну, а кто победит – время покажет.

Я лично склоняюсь к решениям типа C++ или Kotlin, когда UI и логика живут отдельно. Кажется, что это наименьшее из зол на средней и длинной дистанциях. Само собой, с явным предпочтением в сторону Kotlin.

Решения типа Flutter заманчивы, но не хочется тратить силы на борьбу с ветряными мельницами на стыке технологий. Хотя знакомый из HoReCa сказал, что их "мобильщики" счастливы после перехода. Да и @otopba1 кажется вполне довольным от использования.

30.10.2020

See also

Homepage Статьи