Как стать автором
Обновить

Как внедрить KMM в существующие проекты и не пожалеть: теория, наш опыт и гайд

Время на прочтение18 мин
Количество просмотров5.4K

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

Привет! На связи Алексей Михайлов, технический директор компании IceRock Development.

В октябре 2022 года я выступал на самой масштабной IT-конференции Казахстана — Kolesa Conf 2022. Эта статья — транскрипт этого выступления.

За четыре года работы мы сделали больше 20 проектов на Kotlin Multiplatform, а также разработали и поддерживаем свой набор библиотек MOKO для KMM. Это позволяет нам делать мобильные проекты проще и быстрее.

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

Из этой статьи вы узнаете:

  1. Что такое Kotlin Multiplatform Mobile: что включает в себя и что делает

  2. Зачем внедрять KMM в уже запущенные проекты: пять преимуществ

  3. Наш опыт внедрения KMM в существующие проекты

  4. Гайд по внедрению КММ в существующий проект

Что такое Kotlin Multiplatform Mobile: что включает в себя и что делает

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

Kotlin — это язык программирования. Все Android-разработчики хорошо его знают, а iOS-разработчики хотя бы слышали название. «Современный, лаконичный, безопасный» — так этот язык описывают его создатели на своем сайте.

Kotlin Multiplatform — это набор инструментов для этого языка.

Что в него входит:

Первое: три компилятора.

  1. Kotlin/JVM-компилятор, который позволяет запускать Kotlin-приложения на Android.

  2. Kotlin/JS-компилятор, который нужен, чтобы писать на Kotlin веб- или Node.js-приложения.

  3. Kotlin/Native-компилятор, который позволяет компилировать код под iOS, MacOS, Windows и прочие нативные платформы, на которых нет виртуальной машины.

Второе: плагины.

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

  2. Плагин для IDE, который есть в Android-студии и в принципе во всех IDE’шках. Он умеет правильно анализировать Gradle-проекты: обозначать участки общего кода, Android- и iOS-кода, то, как они связаны, навигацию между ними и так далее.

Третье: небольшое расширение самого языка — конструкции expect/actual. Они позволяют написать класс, функцию, интерфейс или другие сущности языка в виде expect в common-части общего Kotlin-кода, а на конкретных платформах уточнить это объявление через actual-декларацию. Например, у вас будет логгер, который можно вызвать из общего кода, но на Android он использует привычный Log.d, а на iOS он будет использовать нативный NSLog. То есть все останется нативно для каждой из платформ.

Kotlin Multiplatform Mobile — мобильная мультиплатформа, не что-то отдельное, а просто подмножество всей большой мультиплатформы.

Ее особенности:

  1. В ней действуют только два компилятора: Kotlin/JVM для Android и Kotlin/Native для iOS.

  2. Есть дополнительный Gradle-плагин для взаимодействия с iOS’ными CocoaPod’ами, если вдруг кому-то захочется подцеплять нативные библиотеки в Kotlin-код. Например, для криптографии.

  3. Добавлен специальный плагин для Android-студии. Он позволяет запускать iOS-приложения из Android-студии и отлаживать их.

Схема работы:

Что забирает компилятор (слева) и во что компилируется Kotlin-код (справа)
Что забирает компилятор (слева) и во что компилируется Kotlin-код (справа)

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

Также можно добавлять и платформенные библиотеки. Например, iOS’ные CocoaPod’ы можно подцепить прямо к Kotlin-компилятору, и он будет с ними взаимодействовать: он скомпилирует такой бинарник, который умеет обращаться к нативной iOS’ной библиотеке.

А на схеме справа вы видите, во что Kotlin-код скомпилируется, на примере мобильных платформ. Для Android он скомпилируется в обычный .jar и будет подключаться как типичная библиотека, написанная на Java или Kotlin. А для iOS он скомпилируется как нативное представление для iOS-разработчиков — фреймворк. Так же как CocoaPod’ы, Carthage и другие менеджеры зависимостей взаимодействуют между собой с помощью фреймворков, если используется динамическая библиотека.

В итоге из Kotlin мы получаем нативную зависимость. Не какой-то свой формат, а то, к чему привыкли Xcode и BuildTool’ы iOS.

Зачем внедрять KMM в уже запущенные проекты: пять преимуществ

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

Как и любая кроссплатформа — React Native, Flutter или другая, мультиплатформа сокращает трудозатраты, время выхода в маркет, снижает количество багов. Но у KMM есть и свои преимущества, которые выделяют ее на фоне остальных.

Преимущество № 1. Конечные пользователи приложения не увидят разницы между обычной нативной разработкой и тем, что вы сделали с помощью мультиплатформы. Мультиплатформа — это набор инструментов для разработчиков, который упрощает и ускоряет разработку, но это не влияет на итоговый результат для самого пользователя. Пользователь видит нативный UI, нативную производительность и никак не сможет отличить приложение с мультиплатформой от приложения без нее. Чего нет у Flutter, например.

Преимущество № 2. Мультиплатформу можно интегрировать с вашим проектом постепенно. Не нужно переписывать весь проект из-за нее. Здесь принцип такой, что вы можете написать любой, даже самый маленький кусочек кода, положить его в общую библиотеку, затем подцепить ее к Android и iOS и использовать из нативного приложения — из Kotlin со стороны Android и из Swift со стороны iOS.

Преимущество № 3. Вы сами решаете, как будет устроено приложение: мультиплатформа не навязывает какой-то свой подход. Вы решаете, какая часть будет в общем коде, а какая останется в нативном и как будет проходить взаимодействие общего кода и нативной части.

Преимущество № 4. Вам не надо сериализовывать данные для того, чтобы передавать их между общей частью и нативной. Мультиплатформа не создает какой-то канал коммуникации платформенной части и общей. Просто есть определенные классы с функциями, у функций есть определенный результат, и вы можете вызывать эти функции напрямую из Kotlin-кода Android или из Swift со стороны iOS.

Преимущество № 5. UI остается нативным. Не утаскивать UI в общий код для кого-то может быть плюсом, для кого-то — минусом. У мультиплатформы уже есть поддержка Jetpack Compose, для iOS в том числе. Она сейчас в режиме разработки и выглядит некрасиво, если честно. Но вы можете скачать приложение Droidcon 2022 от Touchlab в AppStore и посмотреть, как выглядит Jetpack Compose, рисующий на canvas свой UI, как на Android. Выглядит не очень. Но дальше технология будет становиться лучше, потому что ей активно занимаются сами JetBrains — создатели KMM.

Но возможность вынести UI в общий код существует. Общий UI есть от команды Cash App, которая в Android-мире создала OkHttp, Retrofit и прочие популярные библиотеки. Cash App сделали проект Redwood. Он использует Jetpack Compose и позволяет делать UI в общем коде, но на платформах будет использоваться нативный UI. То есть для iOS все будет построено из UIkit, но управление будет осуществляться из общего кода.

Наш опыт внедрения KMM в существующие проекты

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

Кейс № 1: первый опыт интеграции KMM в проект

Первый раз мы решили интегрировать KMM четыре года назад, имея за плечами всего один экспериментальный проект на мультиплатформе, сделанный с нуля.

У нас был проект, который мы развивали. В разработке наступил новый этап, и надо было добавить большой объем функционала. Мы решили узнать, что будет, если мы подцепим мультиплатформу к уже сделанному проекту, чтобы сэкономить хоть чуть-чуть времени.

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

Как все проходило. Android был в своем репозитории, а iOS — в своем, и мы решили не объединять их в монорепозиторий, как на проектах с нуля. Мы сделали дополнительный репозиторий и подключили его как git submodule, и разработчики совместно все это реализовывали. И Android- и iOS-разработчики работали с общим кодом и периодически ломали друг другу компиляцию соседней платформы.

Потом мы дополнительно внедрили несколько правил в процессе и добавили проверку компиляции в CI, чтобы при merge-реквестах у нас компилировались и общий код, и каждая платформа. Так при изменениях разработчики не сломают соседнюю платформу. Трудозатраты у нас тогда сократились на 7% по сравнению с вариантом разработки без KMM.

Кейс № 2: неудача

Второй опыт получился не таким позитивным.

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

Сервер не был документирован, был очень неинформативный API, и он периодически менялся без уведомления. Разработчику одной платформы приходилось делать интеграцию или поправлять сломанное, а потом объяснять разработчикам второй платформы, что он сделал.

Задача: сделать общий код для всей сети. Думали один раз пострадать, но потом нормально использовать готовый API. Хотелось при изменениях со стороны сервера делать изменение в одном месте и не страдать дважды.

Как все проходило. По опыту первой интеграции мы планировали сделать отдельный репозиторий и подцепить git submodule. Потом из Android-приложения, так как оно написано на Kotlin, утащить в этот общий код все, что касается сети. А после подключить iOS-приложение, выкинув все написанное на Swift для работы с сетью и подцепив мультиплатформенную сеть.

Но все пошло не так хорошо.

Мы пошагово следовали нашему плану. Взяли код для Android и утащили из него все, что касается сети. Заменили RxJava на Coroutines, Gson на Kotlinx.Serialization и Retrofit на Ktor. Все, что касалось нативного Android-мира, стало мультиплатформенным. Потом мы сделали интеграцию с Android, увидели, что Android работает, у него есть мультиплатформенный модуль, и он сам работает.

А потом начали цеплять iOS и поняли, что там можно еще столько же времени потратить, сколько мы потратили на Android и KMM. Затраты на это мы не планировали, думали, что будет быстрее.

Наши ошибки:

  1. Не продумали публичный API. То есть что мы будем реально видеть и использовать со стороны платформы — со стороны iOS и со стороны Android. Если бы мы об этом задумались, то уже в тот момент поняли, что приложения на Android и iOS написаны слишком по-разному в данном случае и даже подцепить сеть будет не так просто.

  2. Не сверили подходы к реализации между платформами, и получилось, что код для iOS у нас написан совсем по-другому. Даже сериализация ответов, которые по API были неинформативны, была сделана так, что невозможно было повторить ее на Android. И в итоге тот код, который мы взяли с Android, не подошел. Чтобы заставить все работать, необходимо было написать кучу мапперов. А это огромная трата времени.

Кейс № 3: стали составлять план перехода на КММ

В этом кейсе мы выступали как консультанты. К этому времени у нас уже было множество проектов с нуля и несколько интеграций. К нам пришли за экспертизой ребята из Profi.ru. Они хотели начать использовать мультиплатформу и попросили, чтобы мы проанализировали их Android-чат, который они собирались подцепить к iOS.

Задача: сделать план перевода Android’ного Kotlin-кода на мультиплатформу для дальнейшего подключения к iOS. Ребятам оставалась сама реализация.

Как все проходило. Мы провели ревью и составили план перехода. Команде Profi.ru я дал подробные указания по каждому пункту, здесь ограничусь лишь заголовками с несколькими пояснениями. План выглядел так:

  1. Настройка поддержки Kotlin Multiplatform в Gradle-модуле с чатом.

  2. Перенос платформенно независимого кода в commonMain.

  3. Перенос JVM-зависимых библиотек в commonMain с заменой на мультиплатформенные аналоги.

  4. Перенос JVM-зависимого кода, требующего изменений, в commonMain. Зависимого от java.* классов, а не от внешних библиотек. Там надо заменить на использование expect/actual либо найти какую-нибудь библиотеку, которая бы делала то же самое.

  5. Перенос Android-зависимого кода, требующего изменений, в commonMain. Сделать это можно через expect/actual либо выделяя в общем коде интерфейсы, которые потом будут реализованы на Android и iOS и предоставят нужный функционал.

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

План был для ребят понятен. Мы несколько раз созванивались в процессе работы по итогам ревью, которые я проводил, и обсуждали, как сделать лучше. В итоге они вынесли логику чата, а в дальнейшем и еще несколько модулей. Подробнее об их работе по нашему плану можно прочитать в их статье на «Хабре». В ней ребята описывают совместный опыт и делятся впечатлениями от мультиплатформы.

Кейс № 4: ощутили, как на долгой дистанции затраты на КММ оправдали себя

Последний кейс, о котором я сегодня расскажу, — это наш проект, который мы разрабатываем уже множество лет. Начинался он на Java, потом мы затащили туда Kotlin, а сейчас там появилась Kotlin Multiplatform.

К тому времени мы выполнили несколько интеграций, помимо тех, о которых я рассказал выше, и множество проектов с нуля. Есть Android- и iOS-приложения, которые мы развиваем с 2017 года. И в 2021 году мы решили перестать делать Android и iOS независимо. Работы там было еще очень много, продукт развивающийся, постоянно нужно было добавлять новые фичи.

Задача: внедрить мультиплатформу, чтобы ускориться.

Как все проходило. В этот раз мы тщательно продумали план. Мы собирались взять разработчиков этого проекта, наиболее погруженных в него с обеих сторон — и с Android, и с iOS. Они должны были совместно продумать интеграцию. Итоговым решением снова стал git submodule.

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

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

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

Чаще всего именно в моменте мультиплатформа требует немного больше времени. Но на дистанции дальнейшие изменения этого кода становятся значительно дешевле. Поэтому старые фичи тоже начали перетекать в мультиплатформу.

Гайд по внедрению КММ в существующий проект

Ну и напоследок — инструкция по интеграции своего проекта с мультиплатформой. Это совокупность всего нашего опыта, наших взлетов и падений.

Если вы еще ни разу не работали с мультиплатформой, то не стоит ее сразу тащить в свой проект. Сначала надо наработать базу на своем опыте. Набить шишки, прочитать пачку материалов, разобраться, как она будет работать в похожих на ваш стек условиях. Это включает в себя изучение основ KMM и попытку сделать маленькую копию своего большого проекта, где есть те же самые библиотеки, подходы, конфигурация Android и iOS и так далее. И вот туда надо попытаться затащить мультиплатформу.

1. Изучите основы

В первую очередь надо получить базовые знания о мультиплатформе. Используйте материалы от создателей KMM JetBrains:

Там очень подробно рассказаны все основы.

2. Получите первый опыт работы с мультиплатформой

Шаг 1. Сделайте уменьшенную копию своего проекта для обучения. Должны быть копии и Android-, и iOS-версий. Нельзя смотреть на ситуацию только с одной стороны, ведь проблемы могут прийти с разных. Оставьте одну-две фичи и никак не меняйте архитектурные подходы и зависимости.

Шаг 2. Android и iOS-разработчики должны совместно продумать, как должен выглядеть API общего модуля. Начните с малого. Определите минимум, который туда будет занесен. Это может быть работа с сетью, или какие-нибудь алгоритмы, или сложная математика для графических редакторов. Не бегите за большим количеством шаринга кода, не утаскивайте туда ViewModel или вообще все, кроме UI.

Шаг 3. Решите, как общий модуль будет интегрирован с вашим проектом.

Есть три типовых варианта.

Вариант 1. Монорепозиторий. У вас один git-репозиторий, в котором и Android, и iOS, и общий код — все вместе. Это проще для разработки, если у вас и Android- и iOS-разработчики занимаются разработкой общего кода. Если же команда поделена, то есть код для Android, код для iOS и общий код делают разные команды, то вам больше подойдет вариант 3.

Вариант 2. Git submodule. Этот вариант подходит, если вам хочется сохранить в Git историю Android- и iOS-версий, которые долго развивались и которые не хочется терять. Тогда можно подцепить сабмодулем новый репозиторий с общим кодом, и с ним все будет развиваться точно так же, как в первом варианте.

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

Вариант 3. Нативные менеджеры зависимостей. Этот вариант для больших команд, где можно разделить зоны ответственности — за общий код, за Android и за iOS.

Для Android используется Mavеn-репозиторий. Готовый бинарник из общего кода компилируйте и закидывайте в репозиторий. Цепляйте через Gradle как обычную зависимость.

Мы как-то делали мини-технодемку для ситуации, когда у нас три независимые команды занимались своими областями — Android, iOS и общим кодом. Мы сделали так, чтобы общий модуль публиковался через Gradle-скрипты. Также из Gradle мы запускали всю процедуру публикации в CocoaPods-репозиторий.

CocoaPods-репозиторий — это обычный git, где лежит Pod Spec, и где-то еще лежит сам бинарник. Бинарник мы складывали в Maven-репозиторий. То есть iOS’ный бинарник в виде zip’a валялся в Maven, который как раз подходит для больших файлов. А в git-репозитории CocoaPod’ов лежал Pod Spec, который как раз знает, что бинарник лежит вот по такому-то адресу. Он просто его скачивал, и получалась обычная интеграция CocoaPod’ов.

Для iOS делайте компиляцию нативной зависимости в виде CocoaPod’а, и через поды доставляйте в проект. В таком случае iOS-разработчики ничего про Kotlin так и не узнают. Они не будут участвовать в разработке этого Kotlin-модуля и не будут устанавливать JVM, JDK и прочее себе на машину, чтобы скомпилировать свой iOS-проект. У них ничего не поменяется, просто будет какая-то зависимость, которая имеет какой-то кусок логики.

Шаг 4. Сделайте самый минимум в общем коде. На этом этапе вы получите начальный опыт интеграции. Не откладывайте интеграцию одной из платформ на потом, когда у вас будет куча кода и будет очень больно все интегрировать и думать, как это все подцепить. Лучше сразу на минимуме подцепить, увидеть, что оно работает и как оно выглядит со стороны нативной части.

Шаг 5. Проверьте, что все компилируется и работает.

Шаг 6. Перетащите независящий от JVM код с Android-части в общую либо напишите новый Kotlin-код. Так постепенно вы дойдете до реализации той договоренности, которой команда достигла на первых пунктах: какой в общем коде будет API и какой функционал.

Делайте все постепенно:

  • проверяйте, что все компилируется;

  • проверяйте, как API выглядит из Swift, удобный ли он.

Именно Swift API из-за разницы синтаксиса языков Kotlin API может сломаться и стать неудобным и небезопасным. Например, generic на интерфейсах нельзя делать в публичном API. На Kotlin generic останется, а до iOS generic не дойдет, так как пройдет через Objective-C, в котором нет generic’ов. Это будет уже не так удобно, как на Android с generic.

Подробнее об этом я рассказывал в предыдущей статье.

Шаг 7. Замените JVM-зависимости на expect/actual или на мультиплатформенные, и опять же делайте все постепенно:

  • проверяйте, что все компилируется;

  • проверяйте, как API выглядит из Swift, удобный ли он.

Шаг 8. Интегрируйте общий код с кодом для Android и iOS, когда API достигнет той степени готовности, которую обсуждали разработчики обеих платформ.

3. Примените знания для существующего проекта

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

Шаг 1. Составьте план:

  1. Решите, как подключить общий модуль.

  2. Определите, какие изменения в CI потребуется сделать. Рекомендую не забывать про проверку компилируемости обеих платформ на merge-реквестах, потому что может возникнуть ситуация, когда Android-разработчик свою платформу проверил, а про iOS не подумал. И CI скажет, что вот здесь вот iOS сломался, и надо iOS-разработчика позвать.

  3. Определите, какие фичи проекта будут использовать общий модуль.

  4. Определите, где именно код будет шариться через expect/actual, а где — через интерфейс.

  5. Определите публичный API общего модуля. Android- и iOS-разработчики должны вместе согласовать его. Проверьте, чтобы со стороны Swift это выглядело удобно, корректно и все это билось на итерации с проверками.

Шаг 2. Проведите интеграцию по составленному плану. В результате у вас все должно получится.

Чему уделить особое внимание

  1. Android- и iOS-разработчикам обязательно надо согласовывать API вместе. Потому что Android-разработчик не придумает, как все должно выглядеть на iOS, и наоборот.

  2. Как можно чаще проверять, что изменили в общем коде и работает ли оно теперь на Android и iOS. На первых порах это обязательно. Потом, когда команда наберет достаточно опыта, уже можно будет так сильно не частить.

  3. Данные, которые управляются платформенной частью, лучше передавать, используя интерфейс, объявленный в общем коде. Например, в проекте уже есть процедура авторизации, и нам надо в общий код как-то перетащить токен. Лучше реализовать interface get token на платформе и предоставить этот токен платформенными способами. Так будет лучше, чем писать expect/actual или еще что-то городить.

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

  5. Используйте для UI то, к чему привыкли. Возможно, кому-нибудь покажется, что по примеру JetBrains надо обязательно использовать Compose или Swift UI, но это не так. Бизнес логика — это то, что по умолчанию перетаскивается в общий код, а какой UI к ней будет обращаться, без разницы.

  6. Разделяйте DI общего кода и платформ. Внедрение зависимостей тоже не обязательно делать обобщенным. Можно в общем коде сделать внедрение, используя Koin, Kodein или просто вручную. Мы у себя, например, делаем вручную. И на Android, и на iOS используйте то, к чему привыкли. Например, со стороны Swift — Swinject, а на Android — Dagger. И все вместе будет нормально работать.

Ниже я оставлю несколько ссылок, которые могут помочь вам, если вы работаете с KMM. Отдельно отмечу чат по KMM в «Телеграме», в котором уже больше 1 000 человек, включая разработчиков KMM из JetBrains. Вы всегда можете задать свои вопросы там.

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

Теги:
Хабы:
Всего голосов 4: ↑3 и ↓1+2
Комментарии4

Публикации

Истории

Работа

iOS разработчик
17 вакансий
Swift разработчик
24 вакансии

Ближайшие события

Конференция «IT IS CONF 2024»
Дата20 июня
Время09:00 – 19:00
Место
Екатеринбург
Summer Merge
Дата28 – 30 июня
Время11:00
Место
Ульяновская область