Spec-Zone.ru › Kotlin 2

Перенос приложения Jetpack Compose на Kotlin Multiplatform

Это руководство посвящено переносу приложения только для Android на мультиплатформенную архитектуру во всём стеке — от бизнес-логики до пользовательского интерфейса. В нём на примере продвинутого приложения Compose показаны распространённые сложности и способы их решения. Вы можете подробно следовать последовательности коммитов или бегло ознакомиться с общими шагами переноса и углубиться в интересующие вас части.

В качестве исходного приложения используется Jetcaster — пример приложения для подкастов, созданного для Android с помощью Jetpack Compose. Это полнофункциональное приложение использует:

  • Несколько модулей.

  • Управление ресурсами Android.

  • Доступ к сети и базе данных.

  • Compose Navigation.

  • Новейшие компоненты Material Expressive.

Все эти возможности можно адаптировать для кроссплатформенного приложения с помощью Kotlin Multiplatform и фреймворка Compose Multiplatform.

Чтобы подготовить приложение Android к работе на других платформах, вы можете:

  1. Узнать, как оценить проект на предмет целесообразности переноса на Kotlin Multiplatform (KMP).

  2. Узнать, как разделить модули Gradle на кроссплатформенные и платформенные. В Jetcaster удалось сделать мультиплатформенными большинство модулей бизнес-логики, за исключением некоторых низкоуровневых системных вызовов, которые пришлось отдельно реализовать для iOS и Android.

  3. Проследить, как поочерёдно сделать модули бизнес-логики мультиплатформенными, постепенно обновляя скрипты сборки и код, чтобы переходить от одного рабочего состояния к другому с минимальными изменениями.

  4. Узнать, как перейти к общей реализации пользовательского интерфейса: с помощью Compose Multiplatform можно совместно использовать большую часть кода UI в Jetcaster. Что ещё важнее, вы увидите, как выполнять этот переход постепенно, экран за экраном.

В результате приложение будет работать на Android, iOS и настольных платформах. Настольное приложение также служит примером Compose Hot Reload — способа быстро проверять поведение пользовательского интерфейса.

Контрольный список для возможного переноса на Kotlin Multiplatform

Основные препятствия для возможного переноса на KMP — Java и Android Views. Если проект уже написан на Kotlin и для пользовательского интерфейса используется Jetpack Compose, сложность переноса значительно ниже.

Ниже приведён общий список подготовительных действий, которые следует рассмотреть перед переносом проекта или модуля:

  1. Преобразовать или изолировать код на Java

  2. Проверить зависимости только для Android/JVM

  3. Устранить технический долг в модульной структуре

  4. Перейти с Views на Compose

Преобразуйте или изолируйте код на Java

В исходном примере Jetcaster для Android есть вызовы, доступные только в Java, например Objects.hash() и Uri.encode(), а также широкое использование пакета java.time.

Хотя из Kotlin можно вызывать Java-код и наоборот, набор исходных файлов commonMain, содержащий общий код модуля Kotlin Multiplatform, не может включать код на Java. Поэтому, переводя приложение Android на мультиплатформенную архитектуру, необходимо либо:

  • Изолировать этот код в androidMain (и переписать его для iOS), либо

  • Преобразовать код на Java в Kotlin, используя совместимые с мультиплатформенной разработкой зависимости.

Ещё одна библиотека, специфичная для Java, — RxJava. Она не используется в Jetcaster, но широко распространена. Поскольку это Java-фреймворк для управления асинхронными операциями, перед переносом на KMP рекомендуется перейти на kotlinx-coroutines.

Существуют руководства по переносу с Java на Kotlin, а также инструмент в IntelliJ IDEA, который может автоматически преобразовать код на Java и упростить этот процесс.

Проверьте зависимости только для Android/JVM

Хотя во многих проектах, особенно новых, может быть немного кода на Java, в них часто используются зависимости только для Android. В случае с Jetcaster большая часть работы заключалась в поиске альтернатив и переходе на них.

Важный шаг — составить список зависимостей, используемых в коде, который вы планируете сделать общим, и убедиться, что для них доступны мультиплатформенные альтернативы. Экосистема мультиплатформенных библиотек пока не так велика, как экосистема Java, но быстро развивается. Для оценки возможных вариантов воспользуйтесь сайтом klibs.io.

Для Jetcaster список таких библиотек выглядел следующим образом:

  • Dagger/Hilt — популярное решение для внедрения зависимостей (заменено на Koin).

    Koin — надёжный мультиплатформенный фреймворк для внедрения зависимостей. Если он не отвечает вашим требованиям или объём необходимой переработки слишком велик, есть и другие решения. Фреймворк Metro также является мультиплатформенным. Он может упростить перенос благодаря поддержке взаимодействия с другими аннотациями, включая Dagger и Kotlin Inject.

  • Coil 2 — библиотека для загрузки изображений (в версии 3 стала мультиплатформенной).

  • ROME — фреймворк для RSS (заменён мультиплатформенным RSS Parser).

  • JUnit — фреймворк для тестирования (заменён на kotlin-test).

В процессе вы можете обнаружить небольшие фрагменты кода, которые перестают работать в мультиплатформенной среде, поскольку для них ещё нет кроссплатформенной реализации. Например, в Jetcaster пришлось заменить функцию AnnotatedString.fromHtml(), входящую в библиотеку Compose UI, сторонней мультиплатформенной зависимостью.

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

Устраните технический долг в модульной структуре

KMP позволяет выборочно переводить проект на мультиплатформенную архитектуру — модуль за модулем, экран за экраном. Чтобы этот процесс проходил гладко, структура модулей должна быть понятной и удобной для изменений. Рекомендуем оценить модульную структуру с учётом принципа высокой связности и слабой связанности, а также других рекомендаций по структурированию модулей.

Общие рекомендации можно сформулировать следующим образом:

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

  • Инкапсулируйте данные и бизнес-логику конкретной предметной области в одном модуле. Объединяйте связанные типы данных и не смешивайте логику или данные разных предметных областей.

  • Ограничивайте доступ извне к деталям реализации модуля и источникам данных с помощью модификаторов видимости Kotlin.

При чёткой структуре вы сможете переносить модули на KMP по отдельности, даже если в проекте их много. Такой подход проще, чем пытаться полностью переписать проект.

Перейдите с Views на Jetpack Compose

Kotlin Multiplatform предоставляет Compose Multiplatform для создания кроссплатформенного кода пользовательского интерфейса. Чтобы плавно перейти на Compose Multiplatform, интерфейс уже должен быть написан с помощью Compose. Если сейчас вы используете Views, код придётся переписать, применив новую парадигму и новый фреймворк. Очевидно, что сделать это заранее проще.

Google уже давно развивает и совершенствует Compose. Ознакомьтесь с руководствами по переносу на Jetpack Compose, которые помогут в наиболее распространённых сценариях, или попробуйте навык агента для переноса с помощью ИИ. Также можно использовать совместимость Views и Compose, но, как и код на Java, такой код необходимо изолировать в наборе исходных файлов androidMain.

Этапы перехода приложения на мультиплатформенную архитектуру

После завершения предварительной подготовки и оценки общий процесс выглядит следующим образом:

  1. Перейти на мультиплатформенные библиотеки

  2. Перевести бизнес-логику на KMP.

    1. Начните с модуля, от которого зависит наименьшее количество других модулей.

    2. Переведите его на структуру модулей KMP и начните использовать мультиплатформенные библиотеки.

    3. Выберите следующий модуль в дереве зависимостей и повторите процесс.

  3. Переведите код пользовательского интерфейса на Compose Multiplatform. Если вся бизнес-логика уже мультиплатформенная, переход на Compose Multiplatform будет относительно простым. В случае с Jetcaster мы показываем постепенный перенос — экран за экраном. Мы также показываем, как изменять граф навигации, когда одни экраны уже перенесены, а другие — ещё нет.

Чтобы упростить пример, мы сразу удалили специфичные для Android целевые платформы Glance, TV и wearable, поскольку они всё равно не взаимодействуют с мультиплатформенным кодом и переносить их не потребуется.

Вы можете следовать описанию шагов ниже или сразу перейти к репозиторию с итоговым мультиплатформенным проектом Jetcaster. Каждый коммит соответствует рабочему состоянию приложения и показывает, как можно постепенно перенести его с Android-only на Kotlin Multiplatform.

Подготовьте среду

Если вы хотите повторить шаги переноса или запустить предоставленный пример на своём компьютере, сначала подготовьте среду:

  1. Выполните инструкции из краткого руководства, чтобы настроить среду для Kotlin Multiplatform.

    Для сборки и запуска приложения iOS вам понадобится Mac с macOS. Это требование Apple.

  2. В IntelliJ IDEA или Android Studio создайте новый проект, клонировав репозиторий с примером:

    git@github.com:kotlin-hands-on/jetcaster-kmp-migration.git
    

Перейдите на мультиплатформенные библиотеки

Работа большинства функций приложения зависит от нескольких библиотек. До настройки модулей для поддержки мультиплатформенности можно перейти на их версии, совместимые с KMP:

  • Замените парсер ROME tools на мультиплатформенный RSS Parser. При этом необходимо учесть различия между API, в частности способы обработки дат.

    См. итоговый коммит.

  • Замените Dagger/Hilt на Koin 4 во всём приложении, включая модуль точки входа, предназначенный только для Android, mobile. Для этого потребуется переписать логику внедрения зависимостей в соответствии с подходом Koin, но код за пределами пакетов *.di почти не изменится.

    При переходе с Hilt обязательно очистите каталоги /build, чтобы избежать ошибок компиляции из-за ранее сгенерированного кода Hilt.

    См. итоговый коммит.

  • Обновите Coil 2 до Coil 3. И здесь было изменено относительно мало кода.

    См. итоговый коммит.

  • Замените JUnit на kotlin-test. Это касается всех модулей с тестами, но благодаря совместимости с kotlin-test для переноса потребуется совсем немного изменений.

    См. итоговый коммит.

Перепишите зависящий от Java код на Kotlin

Теперь, когда все основные библиотеки стали мультиплатформенными, необходимо устранить зависимости только для Java.

Простой пример вызова, доступного только в Java, — Objects.hash(). Мы повторно реализовали его на Kotlin. См. итоговый коммит.

Однако главная причина, мешающая сразу сделать код Jetcaster общим, — пакет java.time. В приложении для подкастов вычисления времени встречаются повсюду, поэтому для полноценного совместного использования кода KMP необходимо перенести этот код на kotlin.time и kotlinx-datetime.

Переписывание всего кода, связанного со временем, собрано в этом коммите.

Перенос бизнес-логики

Когда основные зависимости становятся мультиплатформенными, можно выбрать модуль, с которого начать перенос. Полезно составить граф зависимостей модулей в проекте. С этим легко поможет ИИ-агент, например Junie. Для Jetcaster упрощённый граф зависимостей модулей выглядел так:

:mobile

:core:data

:core:data-testing

:core:domain

:core:domain-testing

:core:designsystem

Например, можно придерживаться такой последовательности:

  1. :core:data

  2. :core:data-testing

  3. :core:domain

  4. :core:domain-testing

  5. :core:designsystem — хотя у него нет зависимостей от других модулей, это вспомогательный модуль пользовательского интерфейса, поэтому мы займёмся им, когда будем готовы перенести код пользовательского интерфейса в общий модуль.

Перенос :core:data

Настройка :core:data и перенос кода базы данных

В Jetcaster в качестве библиотеки для работы с базой данных используется Room. Поскольку Room поддерживает мультиплатформенность начиная с версии 2.7.0, нам нужно лишь обновить код, чтобы он работал на разных платформах. На этом этапе у нас ещё нет приложения для iOS, но мы уже можем написать платформенно-зависимый код, который будет вызываться при настройке точки входа для iOS. Мы также добавляем конфигурацию целевых платформ (iOS и JVM), чтобы подготовиться к добавлению новых точек входа в будущем.

Чтобы перейти на мультиплатформенную версию Room, мы воспользовались общим руководством по настройке для Android.

См. итоговый коммит.

  • Обратите внимание на новую структуру кода с наборами исходного кода androidMain, commonMain, iosMain и jvmMain.

  • Большинство изменений в коде связано с созданием структуры expect/actual для Room и соответствующими изменениями DI.

  • Добавлен новый интерфейс OnlineChecker, который позволяет учитывать тот факт, что проверка подключения к интернету выполняется только на Android. Пока мы не добавим приложение iOS в качестве целевой платформы, проверка подключения будет заглушкой.

Можно сразу же перенастроить и модуль :core:data-testing, чтобы сделать его мультиплатформенным. См. итоговый коммит. Для этого нужно лишь обновить конфигурацию Gradle и перейти к структуре каталогов наборов исходного кода.

Настройка и перенос :core:domain

Если все зависимости уже учтены и переведены на мультиплатформенную версию, остаётся только переместить код и перенастроить модуль.

См. итоговый коммит.

Как и в случае с :core:data-testing, модуль :core:domain-testing также легко можно перевести на мультиплатформенную основу.

См. итоговый коммит.

Настройка и перенос :core:designsystem

Когда осталось перенести только код пользовательского интерфейса, мы начинаем переводить модуль :core:designsystem, включая ресурсы шрифтов и типографику. Помимо настройки модуля KMP и создания набора исходного кода commonMain, мы сделали аргумент JetcasterTypography для MaterialExpressiveTheme компонуемой функцией, инкапсулировав вызовы мультиплатформенных шрифтов.

См. итоговый коммит.

Переход на мультиплатформенный пользовательский интерфейс

Когда вся логика :core становится мультиплатформенной, можно начать переносить пользовательский интерфейс в общий код. И снова: поскольку мы стремимся выполнить полный перенос, пока не добавляем целевую платформу iOS, а лишь убеждаемся, что приложение для Android работает с компонентами Compose, размещёнными в общем коде.

Чтобы наглядно представить последовательность действий, ниже приведена упрощённая схема взаимосвязей между экранами Jetcaster:

Home

Player

PodcastDetails

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

См. итоговый коммит.

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

Следуя приведённой выше схеме экранов, мы начали с экрана сведений о подкасте:

  1. Перенесённый экран будет работать, даже если тема Compose останется в модуле Android. Для этого необходимо:

    1. Обновить ViewModel и соответствующий код DI.

    2. Обновить ресурсы и средства доступа к ресурсам. Хотя мультиплатформенная библиотека ресурсов во многом соответствует Android, есть несколько заметных отличий, которые необходимо учесть:

      • Обработка файлов ресурсов немного отличается. Например, каталог ресурсов должен называться composeResources вместо res, а в XML-файлах Android необходимо заменить использования @android:color на шестнадцатеричные коды цветов. Подробнее см. в документации о мультиплатформенных ресурсах.

      • Сгенерированный класс со средствами доступа к ресурсам называется Res (в отличие от R в Android). Переместив и настроив файлы ресурсов, повторно сгенерируйте средства доступа и замените импорты каждого ресурса в коде пользовательского интерфейса.

    См. итоговый коммит.

  2. Перенести тему Compose. Мы также добавим заглушки для платформенно-зависимых реализаций цветовых схем.

    См. итоговый коммит.

  3. Продолжить с главным экраном:

    1. Перенести ViewModel.

    2. Переместить код в commonMain общего модуля пользовательского интерфейса.

    3. Переместить и настроить ссылки на ресурсы.

    См. итоговый коммит.

  4. Чтобы показать ещё один способ разделить перенос на небольшие этапы, мы частично перенесли навигацию. Можно объединить экраны из общего кода с нативным экраном Android. PlayerScreen по-прежнему находится в модуле mobile и включается в навигацию только для точки входа Android. Он внедряется в общую мультиплатформенную навигацию.

    См. итоговый коммит.

  5. В завершение перенесите всё оставшееся:

    • Перенесите оставшуюся часть навигации в общий код (итоговый коммит).

    • Перенесите последний экран, PlayerScreen, в Compose Multiplatform (итоговый коммит).

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

Необязательно: добавление точки входа JVM

Этот необязательный этап позволяет:

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

  • Продемонстрировать Compose Hot Reload — инструмент для быстрой итеративной разработки пользовательского интерфейса Compose, который в настоящее время поддерживается только для целевых настольных платформ.

Когда весь код пользовательского интерфейса общий, для добавления новой точки входа настольного приложения JVM достаточно создать функцию main() и интегрировать её с DI-фреймворком.

См. итоговый коммит.

Добавление точки входа iOS

Для точки входа iOS необходим проект iOS, связанный с кодом KMP.

Создание и встраивание приложения iOS в проект KMP описано в руководстве «Перевод приложения на мультиплатформенную основу».

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

В приложении iOS необходимо связать код Swift UI с кодом Compose Multiplatform. Для этого мы добавляем функцию, которая возвращает UIViewController со встроенным компонуемым элементом JetcasterApp, в приложение iOS.

Добавленный проект iOS и соответствующие изменения кода см. в итоговом коммите.

Запуск приложения

В итоговой версии перенесённого приложения есть конфигурации запуска для исходного модуля Android (mobile) и нового приложения iOS. Настольное приложение можно запустить из соответствующего файла main.kt. Запустите оба приложения, чтобы увидеть, как общий пользовательский интерфейс работает на всех платформах!

Итоги

При переносе мы следовали общим рекомендациям по преобразованию обычного приложения Android в приложение Kotlin Multiplatform:

  • Перейти на мультиплатформенные зависимости или переписать код, если это невозможно.

  • Поочерёдно преобразовать модули Android, которые можно использовать на других платформах, в мультиплатформенные модули.

  • Создать общий модуль пользовательского интерфейса для кода Compose Multiplatform и поэтапно переносить в него пользовательский интерфейс, экран за экраном.

  • Создать точки входа для других платформ.

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

Если у вас есть отзывы о руководстве или представленных решениях, создайте задачу в YouTrack.

21 июля 2026
Превратите приложение Android в приложение для iOS — руководствоСоздание мультиплатформенного приложения с помощью Ktor и SQLDelight

© 2010–2026 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/multiplatform/migrate-from-android.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API