
Внедрение Kotlin Multiplatform (KMM) в процесс разработки
При разработке мобильных приложений всё чаще используется Kotlin Multiplatform Mobile (KMM). Основная идея SDK — оптимизировать процесс разработки, используя общий код бизнес-логики в приложениях iOS и Android. В нём сочетаются мультиплатформенные возможности Kotlin и инструменты, разработанные специально, чтобы сделать создание кроссплатформенных мобильных приложений максимально эффективным и удобным.
KMM позволяет использовать общий код в отдельных частях приложения, оставляя другие абсолютно независимыми. Используя в работе этот SDK, вы получаете удобный инструмент, с помощью которого можно создавать кроссплатформенные продукты с сохранением всех плюсов нативного программирования.
Использование KMM имеет ряд плюсов:
- он не накладывает никаких ограничений на работу с пользовательским интерфейсом приложения. Например, KMM никак не помешает работе с новейшими фреймворками на iOS и Android, SwiftUI и Jetpack соответственно;
- вы можете работать с платформой напрямую. Если определённая задача не может быть решена в кроссплатформенном коде, можно использовать шаблон expect/actual для написания кода для конкретной платформы;
- наши разработчики на Android хорошо разбираются в Kotlin, имеют большой практический опыт работы с этим языком. Если и ваша команда работает на Kotlin, использование KMM логично, вам не придётся добавлять в стек новый язык программирования. Поскольку у языка есть сходство со Swift, разработчики на iOS быстро справляются с изучением Kotlin и сопутствующих продуктов;
- возможности KMM позволяют использовать его как при создании проекта с нуля, так и в уже существующих проектах. Общий Kotlin-код легко добавляется в существующий код как обычная абсолютно любая зависимость.
Исследуем Kotlin Multiplatform Mobile
Мы слышали много хорошего о работе с KMM, но хотели доказать это самим себе, проверить на собственных проектах. Для этого мы решили с помощью KMM реализовать в новом приложении один полный поток.
Реализация одного потока позволит нам оценить:
- обработку асинхронного общего кода;
- использование функции expect/actual;
- интеграцию с Apollo-Android в общем коде;
- работу с Ktor для аутентифицированного HTTP-запроса;
- использование Firebase для отслеживания поведения;
- практику обработки переводов.
В изучении всех этих аспектов хорошо помогает сообщество Kotlin. Там можно получить ответы на любые вопросы. Участники сообщества активно развивают экосистему, создают образцы различных проектов, полезные библиотеки и рассказывают о своём опыте использования Kotlin и KMM в разработке.
Важно отметить, что инструменты разработки KMM постоянно обновляются, появляются улучшения, позволяющие оптимизировать разработку, сделать создание кроссплатформенных приложений проще и удобнее.
Ещё одной нашей задачей было разобраться с архитектурой приложения.
Архитектура
Начав работу над проектом, мы знали, что хотим использовать один и тот же сетевой стек и иметь возможность без проблем разделить код и бизнес-логику между платформами. Приложение создавалось на основе серверной части GraphQL, поэтому работу мы начали с интеграции Apollo в общий модуль. Поскольку мы точно знали, что будем использовать REST, мы также подключили и настроили Ktor.
Используя мультиплатформенные настройки, мы сохранили токены OAuth и написали перехватчик аутентификации для всех сетевых вызовов, будь то REST или GraphQL, iOS или Android. При этом использовались EncryptedSharedPreferences на Android и Keychain для iOS, чтобы обеспечить сохранность токенов на устройстве в зашифрованном виде.
В общем компоненте мы решили использовать SqlDelight, абстрагируя все операции с базой данных из клиентского кода. Весь уровень данных находится в общем коде, нам нужно только предоставить репозитории/интерфейсы платформам.
Архитектура «бета»
Уровень репозитория извлекает данные, необходимые для визуализации пользовательского интерфейса приложений, и абстрагирует все внутренние процессы. Когда ViewModels запрашивает данные, репозиторий может проверить базу данных на наличие кэшированных версий, объединить несколько сетевых запросов, сохранить некоторые данные в локальной базе данных, сопоставить данные с доменными классами и т.д. ViewModels не нужно знать, что происходит за «кулисами», эта модель представления просто заботится о том, чтобы вызванная функция возвращала ожидаемые данные.
@Throws(MeetupError::class)
suspend fun getEventDetails(eventId: String): Event { … }
У приложений Android и iOS есть свои собственные ViewModels для каждого экрана, которые вызывают функции приостановки в общих репозиториях (по одной на экран). При вызове функций приостановки репозиториев Kotlin из iOS мы используем обработчик завершения и закрытие цепочек Swift.
Эти компоненты собраны с использованием фреймворка Koin, что позволяет каждой платформе предоставлять собственную реализацию для общих компонентов (SharedPreferences, Keychain, механизм OkHttp), а ViewModels — получать необходимые репозитории с помощью внедрения конструктора.
Уровни пользовательского интерфейса для Android и iOS используют Jetpack Compose и SwiftUI соответственно, оба для нас относительно новые.
Проблемы
Поскольку это новый проект, все задействованные в нём инженеры были рады опробовать новые технологии, однако у разработчиков Android было явное преимущество, поскольку мы были знакомы с Kotlin. Поначалу инженеры iOS не были в восторге от необходимости работать с Kotlin. Они сосредоточились только на частях пользовательского интерфейса приложения, но со временем их восприятие начало меняться и теперь каждый на регулярной основе вносит свой вклад в общий код KMM.
Это помогло нам увеличить скорость разработки функций. Также мы привлекли к работе дополнительных инженеров Android, чтобы синхронизировать функции обеих платформ и не позволять работе пользовательского интерфейса приложений Android отставать.
Ещё одним неудобством, кроме начального языкового барьера, с которым мы столкнулись, был тот факт, что KMM генерирует код Objective-C для всех общих интерфейсов. При работе со Swift нам пришлось добавить дополнительный слой отображения, чтобы скрыть сгенерированный код и использовать конструкции Swift, к которым мы привыкли. Это излишне усложняет кодовую базу и является потенциальным источником ошибок. В дорожной карте JetBrains предусмотрено добавление прямой совместимости Swift, но пока этого не произойдёт, слой оболочки придётся писать самостоятельно.
Следующие шаги
На данном этапе разработки мы довольны тем, как сложилась для нас работа с KMM. Продукт отправлен первым тестировщикам, и мы начинаем обращать внимание на архитектурные улучшения, которые хотелось бы внести.
Мы рассмотрим возможность извлечения большего объёма кода из отдельных модулей платформы и в общий модуль KMM, возможно, начиная с ViewModels, отображающей состояние пользовательского интерфейса для каждого экрана в виде потока, который можно наблюдать с помощью представлений, используя потоки Kotlin на Android и Combine Publishers в Swift.
Ещё одним вариантом оптимизации, который мы рассмотрим, является использование нового менеджера памяти Kotlin/Native для улучшения потоковой передачи и параллелизма в iOS.
Заключение
Хотя работа с KMM была сопряжена с некоторыми проблемами, в конечном счёте мы довольны своим выбором и результатом. Если вы рассматриваете возможность использования KMM при разработке следующего приложения, мы рекомендуем подход, о котором рассказали выше.
