Всем привет. Меня зовут Борис Вербицкий, и я представитель того редкого типа iOS разработчиков, которые тепло относятся к Kotlin Multiplatform Project и рады появлению Compose Multiplatform. Здесь я решил поделиться своим опытом использования этих технологий, а также кое-какими размышлениями вокруг процессов с такой разработкой. Цель этой статьи - это поднять обсуждение предложенного мной подхода, послушать все за и против в комментариях. Приятного чтения!
Введение
Что же из себя представляет Kotlin Multiplatform Project? Это технология, которая позволяет писать общий код на Kotlin, который впоследствии может компилироваться под разные платформы(Android, iOS, Desktop, Web). Крепится чаще всего, как нативная библиотека через менеджер зависимостей, ведет себя соответственно: можно обращаться/наследоваться/переопределять и расширять публичные классы, интерфейсы. Отлично подойдет для того, чтобы выносить все вплоть до вью-модели в мультиплатформенную часть, а затем женить это с нативной версткой на каждой платформе отдельно.
Compose multiplatform - мультиплатформенный фреймворк, который позволяет написать единый для нескольких платформ UI. По сути, это библиотека с синтаксисом Jetpack Compose для Android, но с движком Skia под капотом, отчего с недавних пор мы можем рисовать интерфейс и для iOS не прибегая к нативным способам. Пока еще находится в alpha стадии для iOS, но уже довольно шустро работает и прекрасно выглядит как временное или быстрое решение, когда бизнесу очень надо.
Если говорить очень грубо, то KMM(KMP) + CMP дает тот же результат, что и Flutter или React Native. KMM позволит вам написать любую бизнес логику или компонент, который вы хотите использовать на нескольких платформах, а CMP дает вам как возможность сделать мультиплатформенный UI как поэкранно, так и полностью весь визуальный интерфейс, включая навигацию. Отличие заключается в том, что вы всегда можете заменить CMP на нативный UI, о чем поговорим позже.
Какие проблемы мы имеем при раздельной нативной разработке?
Пишем один и тот же код для каждой платформы отдельно
Пишем Unit тесты для каждой платформы отдельно (если вообще их пишем)
Одна команда может убежать вперед другой из-за разной производительности, например, из-за разного размера. Могут начаться дискоммуникации, а также отсрочки релизов, если они должны быть синхронными для разных платформ.
Может произойти расхождение функционала
iOS разработчиков сложнее и дороже нанять, а также, судя по данным исследований, зарплаты этих специалистов в среднем на 15% выше (https://habr.com/ru/specials/748058/)
Решает ли KMM(KMP) эти проблемы?
Однозначно да!
95% кода бизнес логики можно написать на Kotlin для обоих мобильных платформ. Всегда можно добавить платформаспецифический код, любой корнер кейс решаем и это доказано опытом огромных приложений: cписок популярных приложений, которые используют KMM(KMP)
Поддержка проекта на KMM(KMP) означает, что если вы правите баг для одной платформы, то и для другой тоже. Правда и сломать можно разом в 2 местах. Если ваш код покрыт тестами, то сделать это сложнее. Unit тесты также будет достаточно написать 1 раз для обеих платформ
Android разработчик базово вкатывается в технологию за 2-3 недели. Имеет смысл собирать команду 1 iOS на ~3-4 Android разработчиков, что означает экономию по деньгам в найме и зп, так как iOS разработчиков нужно меньше.
Прирост скорости разработки от использования этой технологии может быть районе 25% при правильно настроенных процессах, а также в зависимости от проекта. Под правильно настроенными процессами я подразумеваю:
Хранение всего, кроме UI в мультиплатформенном коде, т.e писать на Kotlin.
UI First подход, чтобы утвердить общий state для визуального представления в каждой платформе. State - это либо часть Redux, либо MVI паттерна, описывающий все проперти визуального представления в одном месте. В противном случае могут начаться несостыковки, а значит и постоянные подтягивания бизнес логики под новые вводные UI, что только замедлит разработку, потому что одна платформа будет влиять на другую.
Рекомендую к просмотру опыт Яндекс.Карт в использовании KMM(KMP), там как раз описан подход к процессам с учетом мультиплатформы:
Решает ли проблемы CMP?
Частично.
Так как для iOS CMP еще в alpha версии, есть некоторые проблемы, вот пара из них:
Скролл не выглядит нативно, подлагивает, если есть сильно тяжелые изображения
На текущий момент есть проблемы с двусторонними жестами (https://github.com/JetBrains/compose-multiplatform/issues/3335)
Но надо отдать должное, что статические экраны (без скролла и жестов) выглядят просто прекрасно и вполне могут заменить нативные. Анимация тоже работает отлично.
Как я уже говорил ранее, публичный API Compose Multiplatform и Jetpack Compose абсолютно идентичны, единственный ощутимый минус, который встретит Android разработчик при переходе на CMP - это невозможность использовать мультиплатфоренно accompanist и coil. Если для второго есть отличные замены, то первый надо будет либо реализовать самостоятельно, либо использовать в мультиплатформенном коде через expect/actual.
Получается, что если Android разработчик сразу пишет UI на Compose Multiplatform вместо нативного Jetpack Compose, то мы в подарок получаем сверстанный UI для iOS с некоторыми оговорками. И вот они:
Нужно учесть safeArea для iOS
В случае использования expect/actual надо будет написать отдельную реализацию@Composableфункции для iOS
Если будут нужны какие-то нативные элементы из UIKit или SwiftUI, нужно будет написать обертку для их отображения в CMP.
Это не выглядит дорого, решается просто. Потому лично по моим оценкам CMP отлично подойдет:
Если нужно сделать прототип UI на обе платформы
Если надо протестировать гипотезу, и для начала пользовательский опыт не так важен
Если надо срочно выкатить релиз опять же незначительно жертвуя пользовательским опытом
Написать мультиплатформенные экраны без скролла и жестов вместо нативных
Что с этим всем делать?
CMP отдается нам все в виде ComposeUIViewController, который отлично встает в UIKit навигацию, а также UIKit навигация + SwiftUI на сегодня считается самым надежным решением для SUI, и все это подводит нас к тому, что оба эти подхода можно совместить. А это значит, что можно релизиться с CMP экранами в iOS и по мере течения времени заменять их на нативную верстку. Более того, chatGPT очень хорошо переводит с Compose код на SwiftUI, потому работа для iOS разработчика значительно ускоряется.
То есть если даже у команды Android есть отрыв в 2-3-4-5 экранов, учитывая что можно зарелизиться с CMP экранами в iOS, на общем фоне большого количества нативных экранов это будет практически не заметно. Задача же iOS разработчика не отставать, чтобы отрыв не стал критическим, и это не сказалось на пользовательском опыте.
Если же вы правильно подготовили презентующий слой своего приложения в KMM(KMP) и использовали Redux или MVI, то код будет полностью готов для переезда на UIKit/SUI с CMP.
Что мы получаем?
Увеличение скорости разработки, так как код бизнес логики пишется раз на KMM в 95% случаев, compose код сильно проще переводится в SwiftUI с помощью chatGPT, а также часть CMP экранов без скролла и жестов можно оставить не меняя на натив.
Возможность дешево проводить эксперименты и A/B тестирование с CMP UI
Можно сместить акцент на найм android разработчиков, т.к от iOS разработчика будет требоваться только нативная верстка и платформенные специфический функционал. Экономия на найме и ЗП.
Не будут задерживаться релизы по вине той или иной команды, так как функционал будет достаточный для выкладки в AppStore/Google Play благодаря CMP
Не будет расхождения в функционале
Поддержка обойдется дешевле, так как писать код бизнес логики и тесты придется только 1 раз
Общий прирост скорости разработки может увеличиться на 35-40% с меньшими затратами по деньгам.
Безусловно, чтобы эта магия заработала, нужны инвестиции в:
Освоении мультиплатформенных библиотек и самой мультиплатформы
Постройки процессов под такой тип разработки
По своему опыту скажу, что удивление в мультиплатформенной разработке у меня вызвал только gradle и sqlDelight, так как для работы с ним надо знать SQL, с которым, кстати, тоже хорошо помогает chatGPT. Но даже SQL мне роднее CoreData. В остальном, coroutines тот же structured concurency и очень похож на async/await из мира iOS, ktor для сетевых запросов интуитивно понятен и даже более приятен, чем URLSession, DI и в Африке DI, потому лично у меня сложились очень приятные впечатления об основном стеке в KMM(KMP). Ещё больше библиотек вы сможете найти тут.
Бонус
Кстати, у KMM(KMP) и CMP есть еще и desktop таргет, который полностью stable, потому, если вы написали бизнес логику для мобильный приложений, а также у вас есть готовая дизайн система, то вам не составит огромного труда собрать приложение для desktop. О web говорить сложнее, но уж бизнес логикой вы точно сможете поделиться точно также.
А каковы перспективы?
Совсем недавно было объявлено, что KMM(KMP) в следующем году перейдет в Stable, а также уже где-то не за горами CMP beta. Более того, с этого года KMM(KMP) официально поддерживается Google, что внушает оптимизм, а также уже совсем скоро нас ждет K2 компилятор для Kotlin. Ходят слухи, что именно он является камнем преткновения, чтобы интеропить код из Kotlin напрямую в swift, а не objetctive - c.
Вывод
Я очень доволен мультиплатформенной разработкой и ни разу не пожалел, что потратил столько времени на изучение этих технологий. По моему опыту, чтобы iOS разработчику чувствовать себя также уверенно, как android разработчик в KMM/CMP, нужно потратить примерно 4 месяца. Но если бы я сейчас проектировал приложение с нуля, то я точно бы начал с этих технологий, потому что KMM(KMP) и CMP - это не компромиссная мультиплатформа, а та, что дополняет нативный код и встраивается в существующее приложение. Очевидно, что больших приложений alpha не годится, но если это приложения среднего размера или заказная разработка, то KMM(KMP) + CMP - это ваш выбор!
Очень интересно послушать чужой опыт, а также мнение в комментариях. Благодарю за внимание!
Комментарии (20)
Rusrst
14.09.2023 17:04Compose действительно хотелось бы попробовать в мультиплатформе, но останавливает отсутствие Mac. Раньше не обзавелся, сейчас дороговато. + то что он ещё в альфе все же.
mimorealnogo Автор
14.09.2023 17:04+1Можно начать с desktop таргета, тоже интересный опыт. Он полностью stable
glider_skobb
14.09.2023 17:04+2Для использования CMP не обязателен Mac. Только если вы собираетесь разрабатывать под iOS. А судя по отсутствию мака, это не ваш случай)
В этой ситуации вам все ещё будет доступны Android, desktop и web (js или wasm) таргеты.
gev
14.09.2023 17:04Я правильно понимаю, что Compose Multiplatform и Jetpack Compose это два разных композа? И первый не нативен для Android также как и для и iOS в отличии от Jetpack Compose. А Skia под капотом исползуется для обеих платформ?
mimorealnogo Автор
14.09.2023 17:04Не совсем так, Compose Multiplatform работает поверх Jetpack Compose для Android. Для всего остального(web, desktop) CMP работает поверх Skia. То есть для android CMP условно нативная технология, android разработчик почти не заметит разницы между CMP и jetpack compose во время работы
nullskill
14.09.2023 17:04Смотрел презу CMP на Мобиусе, для iOS все выглядело максимально грустно и костыльно. Докладчик просто копипастил андроидовский дропдаун компонент для iOS, потому что такого просто не существует и в целом компонентов под нее раз-два и обчелся, насколько я понял... Что будет с производительностью тоже, мягко говоря, вопрос. На Flutter уже обожгись с jank'ами в Skia на яблочной платформе, поэтому и переходят на Impeller. В CMP как планируют решать эту проблему, или пока не до этого, выпустить бы бету?
mimorealnogo Автор
14.09.2023 17:04+1Да, самое главное, что заметит android разработчик при переходе на CMP - это то, что нету этих самых дропдаунов и тд. Для Android решается очень просто через expect/actual @Composable функцию, где для android можно подкинуть ту самую, что копировал выступающий из натива (т.е без копирования). Для iOS придется писать свою реализацию, или скопировать, как это было в примере на конференции
На Skia на CMP будут те же самые проблемы, что и в Flatter, когда он на Skia. Это связанно с устройство самой Skia, потому что там нету возможности компилировать шейдеры заранее, в лучшем случае прогревать их, как это делали на Flutter. Про переход CMP на impeller не знаю, думаю, что пока не выкатят beta, а потом stable, речи о переходе на другой движок не будет
qoj
14.09.2023 17:04+2Нет, компоуз в природе только один, его написала гугл. Только там почему-то решили мультиплатформенный код собирать только под андроид таргет.
Чтобы получить CMP jetbrains затягивает в свой форк новую версию, выносит весь мультиплатформенный код в common (а в последнее время как я понимаю даже это не так часто приходится делать, т.к. в гугле уже пишут его в common), и полирует релиз для всех остальных таргетов. В случае подключения CMP к андроид проекту gradle загружает не мультиплатформенные артефакты от JB, а гугловые. Т.е. для андроида нет разницы какой компоуз в проекте CMP или нет, приложение всегда собирается с гугловым.
alexpurs1980
14.09.2023 17:04А как на КММ встраивать БД для iOS и Андроид одновременно?
mimorealnogo Автор
14.09.2023 17:04+1Ну начать стоит с:
https://github.com/cashapp/sqldelight
Эта мультиплатформенная библиотечка работает поверх sqlLight, потому справляется прекрасно.
Если есть какие-то специфические вещи, которые она не решает (а я с ходу не могу придумать какие), то можно создать мультиплатформенный интерфейс, написать реализацию на каждой платформе отдельно и прокинуть в KMM проект. Из плюсов при правильном построении архитектуры можно будет написать тесты для БД раз для обоих платформ
Лично мне для всех моих случаев было достаточно sqlDelight
nullskill
14.09.2023 17:04Без обид, на фоне Flutter это выглядит мышиной возней, без каких-либо обозримых перспектив в проде.
mimorealnogo Автор
14.09.2023 17:04+1Обид нет????
Все зависит от задачи: если нас устраивает тот user experience, который дает Flutter, и у нас нет планов переходить на натив, то Flutter однозначный победитель. В таком случае нужна команда Flutter разработчиков.
Если же мы работаем над приложением, которое будет развиваться, и нас интересует нативное поведение UI, то мы однозначно выбираем KMM(KMP) + CMP. Потому что в таком случае мы получаем бонусы для iOS, которые я описал в статье просто потому, что android разработчик делает свою работу на нативном для себя фреймворке. У нас (iOS разработчиков) будет возможность взять уже готовую бизнес логику KMM(KMP), готовые экраны на CMP, втащить их временно в прод, а в процессе переписывать CMP на SUI, например, с chatGPT.
Звучит сложнее, чем просто написать приложение на Flutter, но найти команду на Flutter тех размеров, какие сейчас есть команды в больших компаниях просто невозможно. Потому не понятно, что еще сложнее
ogregor
14.09.2023 17:04Чем вам React Native не нравится. С точки зрения затрат на разработку и поддержку он не имеет конкурентов. Разработчиков найти проще простого. Любой мидл фронтенд освоит его прямо в процессе разработки.
mimorealnogo Автор
14.09.2023 17:04+1Тем, что Android разработчики уже есть в команде и их не надо нанимать. Достаточно только внедрить технологию и настроить процессы, чем искать целый штат.
Если мы говорим про приложение с нуля, когда команды еще нет, то я тоже бы предпочел, чтобы мои коллеги были ближе к нативу, чем фронтендеры, которые решили переучиться в мультиплатформенную мобильную разработку. Последние неизвестно когда въедут, потому что платформенные нюансы будут сыпаться еще долго. К тому же у меня нет ответа на вопрос: "Зачем им это надо?"
Safort
В таких обзорных статьях хотелось бы хотя бы поверхностного сравнения с Flutter, ибо он выглядит стабильнее уже прямо сейчас.
hVostt
Могу пару слов на эту тему сказать. Flutter достаточно стабилен, чтобы писать production-ready приложения. Вкатиться во Flutter ещё проще, так как можно выбирать не только среди Android/iOS разработчиков, но ещё и разработчиков Angular. Всё остальное +/- тоже самое.
mimorealnogo Автор
Вы правы, если мы будем сравнивать Flutter с CMP без дополнительного контекста. Если лоб в лоб, то Flutter куда более развитый и стабильный, чем CMP, потому что получить рабочее приложение на нем будет гораздо проще.
Но есть пара моментов:
1) Нативный UI для iOS лучше Flutter и CMP во всех случаях. Мало какие компании готовы на user experience, который дает Flutter, потому постепенный переезд на натив обязательная необходимая опция.
2) Синтаксис CMP 1 в 1, как Jetpack Compose, который нативен для Android. Если android разработчик будет сразу писать под Android под CMP, то он не заметит разницы, а эффект будет точно таким же. CMP для Android работает поверх Jetpack Compose, а значит дает тот же эффект, что и натив.
3) Сегодня для новых экранов и приложений android разработчик с гораздо большей вероятностью выберет Jetpack Compose, чем View(xml)
Получается, что если мы добавляем к выбору библиотеки еще и контекст набора команды и построения процессов, то:
Flutter куда менее пригоден, чтобы шарить только бизнес логику на несколько платформ, чтобы потом поверх нее построить нативный UI. KMM(KMP) с этим справляется гораздо лучше. Т.E с Flutter будет сложнее слезть.
Чтобы Flutter появился в проекте - надо нанять Flutter разработчиков (и где-то их еще найти), когда как Compose и так сейчас заходит в проекты, так как это нативная для Android технология. При небольшой подготовке мы получаем CMP код для iOS в подарок просто потому, что android разработчик делал свою работу.
И вот вопрос, если Jetpack Compose и CMP - это одно и то же, и я как iOS разработчик получаю почти бесплатно экраны для iOS просто потому, что android разработчик занимается своей работой, то почему бы мне этим не воспользоваться?
Во-первых, я смогу подкинуть готовые экраны уже сейчас, в которых нет скролла/работы с жестами
Во-вторых, я смогу подкинуть все остальные экраны тоже, но временно, чтобы не срывать релиз, если вдруг не успеваю. А с выходом в beta проблем у этой технологии станет еще меньше.
Во-третьих, chatGPT переводит CMP в SUI на раз два
Даже несмотря на то, что Flutter как технология старше и стабильнее, концептуально с точки зрения расходов и настройки процессов он на мой взгляд не очень удачен.
ReyzoR
"Flutter достаточно стабилен, чтобы писать production-ready приложения."
Да, но я бы по опыту сказал что только для проектов у которых маленький жизненный цикл - написал, внедрил, забыл.
Вел проект около 2-3х лет, и постоянно вылезали проблемы по типу - для того чтоб обновиться нужно чтоб все либы обновились. А таких breaking changes уже было два: при переходе с дарта без наллсейфити на наллсейфити, и сейчас с 2 на 3. И не дай бог у вас в проекте есть какая то либа которую забросили год назад. Начинаются истории с локальным форканьем проекта и его поддержка садиться на ваши плечи. Как по мне 2 раза за такой короткий срок ломать начистую обратную совместимость для продакшен решения - не комильфо.
Все таки одним из минусов докинул бы что вскользь упоминал автор - если вдруг придется писать нативную реализацию самим (нет нужной библиотеки, или есть но не удовлетворяют бизнес-требованиям) это превращается в борьбу с платформой. Даже с учетом возможности использовать Pigeon - местами превращается в борьбу и написание "прослойки", когда в KMP это более проще выглядит.