Перейти к содержанию

Общий и платформенный код

KMP допускает несколько полезных границ переиспользования. Это варианты, а не ступени зрелости. Гибридный продукт не является неудачной попыткой переиспользовать всё.

Стратегия Общее Платформенное Типичный сценарий
Shared core Модели, форматирование, алгоритмы Большая часть приложения Библиотеки, эксперименты, первое внедрение KMP
Shared data API, сериализация, database logic, repositories Presentation и UI Постепенное внедрение
Shared data + domain Data, repositories, бизнес-правила, use cases ViewModels и UI Независимые native presentation-команды
Shared presentation Data, domain, state holders, UI state Нативный UI и навигация Одинаковое поведение при нативном UI
Shared UI Большая часть приложения и Compose UI Entry points и интеграции Небольшие cross-platform команды и близкие продукты
Hybrid Отдельные features, слои или UI-компоненты Всё остальное Зрелые продукты с разными требованиями платформ

Граница по ответственности

Data sources и repositories

Поведение HTTP client, сериализация, DTO, SQL-запросы, cache policy, pagination, retry и синхронизация часто хорошо переносятся в общий код. Database drivers или factories, пути файловой системы, secure storage, наблюдение за сетью, background work и push notifications обычно требуют платформенных adapters. Точная граница зависит от поддержки targets библиотеками и требований продукта.

Repositories особенно полезно переиспользовать, когда они задают single source of truth, координируют remote и local data, реализуют offline-first и freshness policies, а также преобразуют DTO, database- и domain-модели. Так платформы разделяют бизнес-поведение, а не только transport code.

Domain и presentation

Domain models, валидация, фильтрация, сортировка и реальные бизнес-правила обычно переносимы. Use cases должны представлять значимые операции или orchestration. Не нужно оборачивать каждый метод repository только ради формальной схемы слоёв.

В presentation можно переиспользовать immutable UiState, actions, state holders или ViewModels, а также поведение loading, error, offline, retry и recovery через StateFlow. Сложными границами остаются lifecycle ownership, saved state, navigation, permissions, соглашения SwiftUI и Compose и Kotlin/Swift interoperability.

UI может оставаться Jetpack Compose плюс SwiftUI, использовать общие экраны Compose Multiplatform, переиспользовать отдельные компоненты или разделять Compose UI только между Android и Desktop при нативном SwiftUI на iOS.

Interfaces и expect/actual

Для поведения и заменяемых зависимостей - storage, connectivity, clock, analytics, external links и platform services - удобны interfaces с внедряемыми реализациями:

interface ExternalLinks {
    fun open(url: String)
}

class HelpPresenter(private val links: ExternalLinks) {
    fun openHelp() = links.open("https://example.com/help")
}

expect/actual подходит для узкого bridge, когда API имеет одинаковый смысл, но разную реализацию:

// commonMain
expect fun platformName(): String

// androidMain
actual fun platformName(): String = "Android"

Expected и actual classes сейчас имеют статус Beta. Functions и properties всё равно могут быть полезным узким bridge, но interfaces проще заменять, подделывать в тестах и реализовывать несколько раз на одной платформе.

Практическое правило: предпочитайте interfaces для поведения и зависимостей. Используйте expect/actual как узкий bridge к platform API, а не как замену архитектурным абстракциям.

См. Структура проекта и Source Sets и Архитектура и стратегии UI.

Источники