Часть I

Это серия статей о Kotlin Multiplatform Mobile (KMM), которые вам следует знать, если вы хотите начать его использовать. Они были разделены на три части:

  • Часть I. Введение в мультиплатформу Kotlin
  • Часть II: Конфигурация KMM
  • Часть III: пример KMM (кэш + сеть)

Пункты, которые будут рассмотрены в первой части

  • Что такое мультиплатформа Kotlin (KMP)?
  • КМП против КММ
  • Какие проблемы пытается решить КММ?
  • Как структурировать код с помощью KMM?
  • Каковы ограничения использования KMM?
  • Заключение

Что такое мультиплатформа Kotlin (KMP)?

Он был представлен в Kotlin 1.2 17 ноября (см. Блог Kotlin) и согласно Kotlin Documentation:

Поддержка многоплатформенного программирования — одно из ключевых преимуществ Kotlin. Это сокращает время, затрачиваемое на написание и сопровождение одного и того же кода для разных платформ, сохраняя при этом гибкость и преимущества нативного программирования.

Другими словами, KMP — это способ совместного использования кода между платформами, такими как уровни бизнеса, данных и представления, и написание собственных компонентов только для определенных платформ. При этом вы можете написать компонент KMP, инкапсулирующий бизнес-логику (например, компонент входа), и его могут использовать определенные платформы.

КМП и КММ

Вероятно, первым термином, который вы услышали, был Kotlin Multiplatform (KMP), а затем термин Kotlin Multiplatform Mobile (KMM). Но есть ли различия между ними? Найдем ответ!

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

Как видите, КММ — это специализация КМП. Это означает, что KMP может компилироваться в собственный двоичный код для конкретной платформы (см. полный список платформ, которые поддерживает kotlin), но KMM является частью KMP, отвечающей за мобильные цели (Android + iOS). Для этого есть инструменты, плагины, библиотеки и т.д.

Какие проблемы пытается решить КММ?

Поскольку KMM предназначен для мобильных целей, проблема, которую он пытается решить, заключается в том, чтобы разделить то, что нужно разделить между платформами, часть того, что нужно разделить, является обязанностью разработчика. Это означает, что вы можете поделиться только определенным модулем или несколькими из них вместе (включая пользовательский интерфейс, подробнее об этом позже). Давайте посмотрим на пример.

Допустим, я хочу разработать приложение, которое отправляет информацию об устройстве на любой удаленный сервер. Без КММ это выглядит так:

Судя по последнему рисунку, уровень представления использует определенные API-интерфейсы Android и iOS для получения информации с устройства.

Затем слой domain использует эту информацию из уровня представления, чтобы создавать объекты с определенным форматом и добавлять дополнительные бизнес-правила, если это необходимо (например, добавление определенного формата для некоторых атрибутов или если некоторые датчики недоступны, вероятно, мы должны отправить пустые данные).

Наконец, уровень data отвечает за отправку информации на сервер. В случае с Android мы можем использовать Retrofit, а для iOS — Alamofire. Вероятно, вы начинаете замечать некоторые общие шаблоны в этом приложении, особенно для уровня домена и данных.

Теперь давайте посмотрим на то же приложение, но теперь с использованием KMM.

Как видите, используя KMM, мы избегаем повторения кода на уровне домена и данных. Эти два слоя можно написать и использовать в Kotlin, в случае domain это может быть чистый модуль Kotlin, а для слоя data он может быть написан на Kotlin и использовать клиентскую библиотеку Ktor для отправки информации на сервер.

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

Как структурировать код с помощью KMM?

Структурирование кода/модулей является обязанностью каждого разработчика. Но, ища примеры в Интернете, я нашел некоторые общие шаблоны, которыми имеет смысл поделиться в контексте KMM. Итак, начнем!

Совместное использование бизнес-логики + конкретная целевая платформа

Вероятно, это самый распространенный и популярный вариант использования KMM. Используя эту структуру, вы можете создавать собственные компоненты пользовательского интерфейса, используя собственный набор инструментов для каждой платформы (например, Jetpack Compose в Android и SwiftUI в iOS), а общий модуль может добавлять определенные функции в зависимости от платформы, используя что-то, называемое фактическое/ожидаемое функция. .

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

Совместное использование бизнес-логики + ViewModel (уровень представления)

Что касается уровней представления, существуют различные архитектурные шаблоны (MVVM, MVC, MVP и т. д.), которые можно применять для структурирования кода. Давайте воспользуемся MVVM в этом случае и посмотрим на следующую картинку.

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

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

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

Это наиболее распространенные шаблоны с использованием KMM, но их больше, и их также можно настроить в соответствии с вариантом использования.

Каковы ограничения использования KMM?

  • ViewModel: Собственно говоря, ViewModelв Android должен расширяться от ViewModel, а для iOS мы должны использовать протокол ObservableObject. Если эти ViewModels перемещены в общий модуль. вероятно, вы не можете реализовать оба (ViewModel и ObservableObject) вместе в одной ViewModel. Таким образом, чтобы поделиться ViewModel, вы можете реализовать шаблон проектирования (наблюдатель, подписчик публикации и т. д.) или использовать другую библиотеку, которая инкапсулирует эту функциональность (например, https://github.com/dbaroncelli/D-KMP-sample)
  • Наблюдение за изменениями атрибутов: внутри ViewModel обычно есть атрибуты, которые постоянно изменяются и отслеживаются пользовательским интерфейсом. В случае с Android это можно сделать, используя LiveData, а для iOS — пометив атрибут @Published, и проблема такая же, как и предыдущая. В общем модуле, вероятно, нет возможности использовать оба атрибута в одном атрибуте, и решением будет использование другой библиотеки или реализация шаблона проектирования.
  • Сопрограммы: давайте на мгновение представим, что ViewModel не используется совместно. Таким образом, мы можем реализовать его на каждой платформе изначально. Но в большинстве случаев приложение должно выполнять некоторую работу вне потока пользовательского интерфейса, делегируя ее какому-либо механизму фоновой работы. В Android, вероятно, наиболее распространенным в настоящее время является coroutine, а для iOS — Actors. Поскольку общий модуль использует Kotlin в качестве основного языка, в Android мы можем безопасно использовать сопрограммы или потоки, но iOS не знает о потоках и сопрограммах. Хорошая часть заключается в том, что мы можем использовать обратные вызовы для iOS, а также есть статья о возможном сильном решении, написанная @joreilly, под названием Преодоление разрыва между параллелизмом Swift 5.5 и Kotlin Coroutines с помощью KMP-NativeCoroutines.
  • Проблема модульности в iOS. По-видимому, возникает проблема, когда приложение iOS пытается использовать один и тот же модуль общего доступа в разных модулях. По сути, KMM создает один и тот же модуль, но несколько раз, вместо того, чтобы делиться только одним. Более подробно вы можете прочитать в этой статье, написанной Евом Канивцом.

Заключение

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

Кроме того, есть большие проекты, которые используют KMM в производстве (например, Netflix, Phillips и больше), и, возможно, это не упоминалось, но модульное тестирование также возможно с использованием KMM.

По сути, подумайте о KMM как о новой возможности обмена кодом между платформами, который будет улучшен сообществом Kotlin, и у вас есть контроль над тем, что нужно совместно использовать, а что нет.

Рекомендации