Expert JS React Developers for TUI wanted. Join Ciklum and get a $4000 sign-on bonus!
×Закрыть

Kotlin код в мультиплатформенной разработке. Инсайты Android-разработчика

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті.

Привет! Я — Вадим Савченко, Android developer в NIX.

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

Как часто водится на аутсорсе, заказчик хочет готовое решение быстро, качественно и от одного разработчика. Kotlin Multiplatform Mobile (далее — КММ) экономит время и усилия и помогает достигнуть желаемого результата. Команда NIX давно успешно использует Kotlin в коммерческих проектах. На этот раз мы решили пойти дальше и узнать, какие возможности дает КМM.

Суть подхода заложена в слогане на официальном сайте КММ: Save time and effort by writing the business logic for your iOS and Android apps just once. Мы можем создавать модули с общим кодом и подключать их к разным нативным приложениям на Android и IOS.

Однако «‎чистый» Kotlin не так прост, как кажется. Нетрудно отказаться от Android импортов в слое бизнес-логики, но гораздо сложнее свыкнуться с мыслью, что Java импортов тоже не должно быть. Мы посмотрели на KMM в разрезе Clean Architecture. Здесь есть явное отделение слоя бизнес-логики от остальной части приложения (слой Domain). Разбиение на слои мы сделали с помощью модулей. Так проще проконтролировать, чтобы лишний импорт и платформозависимый код не попали, куда не следует. Получили три модуля — Presentation, Domain, Data:

  • Presentation — это модуль с презентационной логикой и вьюшками;
  • Domain — бизнес-логика;
  • Data — репозитории и датасорсы.

Domain и есть наш KMM модуль, написанный на «‎чистом» Kotlin. Собрать его без проблем можно под IOS и Android. Presentation и Data — платформозависимые модули и не могут быть переиспользованы... Но это не точно. Мы решили выяснить, могут ли Data и Presentation стать KMM модулями на основе Pure Kotlin.

Сначала из Presentation выделили еще один модуль UI и закинули в него Views, Activities, Fragments. В Presentation же оставили MVP контракты и реализации презенторов. Зачастую в них можно обойтись без Android импортов. Затем из Data выделили Infrastructure. Здесь у нас DataSources и реализации Repositories, требующие Java или Android импортов. В Data остаются модельки, которыми оперирует приложение, и контракты Repositories. В итоге наши догадки оправдались: Data и Presentation тоже готовы к переиспользованию.

KMM позволяет сделать мультиплатформенными Infrastructure и View, но с ними дела обстоят сложнее. Создадим еще один простой KMM модуль для логирования. После этого вместо привычного java -> main у вас появится commonMain. androidMain и iosMain. Как же их использовать?

//Common
expect class MultiLogger() {

   fun logError()
}

//Android
actual class MultiLogger {

   actual fun logError() {
       Log.e("ERROR", "ERROR ANDROID")
   }
}

//IOS
actual class MultiLogger {

   actual fun logError() {
      println("ERROR IOS")
   }
}

В commonMain лежит «‎чистый» Kotlin код. В случае с Domain весь код будет лежать здесь, а androidMain и iosMain нам вообще не понадобятся. Так как мы хотим, чтобы для Аndroid логирование происходило через стандартный Log, а не println(), нам нужны androidMain и iosMain. Для реализации в commonMain создали класс MultiLogger и обозначили его ключевым словом еxpect. В androidMain объявили класс MultiLogger и реализовали лог с помощью привычного нам Log. В iosMain используем println и надеемся, что он нас устроит :).

По аналогии с логером то же самое можно сделать с UI модулем и Infrastructure. Задача такого разбиения — отделить то, что может быть собрано под Android и IOS и в будущем легко реализовано на каждой из этих платформ. Если не хотите вручную писать View отдельно для двух платформ, воспользуйтесь готовыми решениями. Например, moko-widgets. Так же и с Data слоем, есть библиотека moko-permissions, которую можно взять для ваших репозиториев и не прибегать к expect и actual.

О чем нам никто не сказал

Когда мы впервые столкнулись с Kotlin, нас ждало несколько сюрпризов. Расскажу вам о них, чтобы вы были ко всему готовы :)

Из минусов — мультиплатформенные проекты требуют новейшей версии системы сборки Gradle. Переход к старым проектам затрудняется. Если вы уже переключились на мультиплатформу, но через какое-то время вам надо вернуться в прошлый коммерческий проект, сделать это будет сложно. Gradle вечно что-то кеширует, приходится постоянно чистить кеш, но чаще это не помогает. Ок, переустанавливаем. Но на все это уходит слишком много времени.

Кроме того, нам пришлось вручную формировать пакеты и структуры папок под три Source-набора: Android main, iOS main и Common main. Также было неудобно самим создавать build.gradle.kts и прописывать в них пути к исходникам.

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

Если до этого момента вы успешно откладывали знакомство с Coroutines, здесь уже без них никак. С помощью Корутинов на Kotlin пишут асинхронный, неблокирующий код. У нас с ними все было ОК. Но коллеги рассказывали, что иногда Coroutines могут зависнуть в iOS по непонятным причинам, и это сложно предотвратить и контролировать. Насколько я знаю из новейших источников, этот момент пофиксили. Но есть другая проблема — iOS код может выдавать ошибки, которые превращаются в базовые iOS-ные.

Сейчас почти все проблемы ушли. Вы можете использовать последнюю версию android studio, и все будет прекрасно работать. Также Kotlin Multiplatform Mobile Plugin существенно упрощает создание новых мультиплатформенных проектов и поддержку текущих и даже позволяет дебажить код, собранный под IOS.

Подводя итоги, хотелось бы сказать, что все Android-разработчики давно привыкли использовать Kotlin. Круто, что совсем немного изменив нашу структуру модулей, мы можем получить код, который будет переиспользован. Так что давайте пробовать, заводить тикеты о проблемах, с которыми сталкиваемся, поднимать комьюнити и учиться на ошибках друг друга :)

👍НравитсяПонравилось8
В избранноеВ избранном1
Подписаться на тему «Kotlin»
LinkedIn

Похожие статьи

Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Нужно ли UI часть для IOS писать на Swift только, или можно использовать Flutter?

Можно на Flutter, но тогда уже логичнее писать на Kotlin общую логику, а UI для обоих платформ на Flutter.

В таком случае логичнее и общую логику на Dart писать)

Тут в статье, хотелось сделать акцент на том, что небольшое изменение привычек позволит в случае необходимости легко перейти на KMM, но в целом согласен с вами)

зачем писать на дарте, если он ничем не лучше а иногда даже хуже?

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

В Presentation же оставили MVP контракты и реализации презенторов. Зачастую в них можно обойтись без Android импортов.

Этот паттерн мало того, что ведёт к необходимости писать кучу кода для описания интерфейса вьюх, который у вас как раз и не получится переиспользовать, так ещё и больно бьёт в кейсах поворота экрана.
Презентейшн лейер — это по определению платформо-зависимый модуль.
По моему мнению, хорошим решением было бы описать view states в виде Котлин классов и просто передавать их во viewModels.
Это позволило бы использовать MVVM, убрать все эти контракты и сбиндить вьюху с презентейшн напрямую. А viewState передавать из кроссплатформенного кода.

Добрый вечер, да согласен mvvm приятнее в сохранении состояний)
Я считаю что презентейшн — является платформозависимым если он отвечает за:
1. Рендеринг вьюх
2. Презентационную логику
В нашем подходе мы хотели минимизировать платформозависимый код поэтому разделили эти ответственности на два модуля, в UI модуле у нас остался только рендеринг то-есть реализации View контрактов из Presentation (немного не понял почему контракты не сможем переиспользовать)

Подписаться на комментарии