Мультиплатформенная ViewModel
Android ViewModel позволяет связать бизнес-логику приложения с компонентами пользовательского интерфейса. С Compose Multiplatform вы также можете использовать ViewModel в общем коде.
На этой странице описано, как настроить и использовать ViewModel в мультиплатформенном проекте:
Выберите, какую часть кода ViewModel и пользовательского интерфейса сделать общей: от полностью общего подхода до совместного использования только репозитория или слоя данных.
Настройка зависимостей
Чтобы сделать ViewModel и пользовательский интерфейс общими для разных платформ:
-
Определите зависимости в файле каталога версий Gradle:
[versions] androidx-viewmodel = "2.10.0" [libraries] androidx-lifecycle-viewmodel-compose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "androidx-viewmodel" } androidx-lifecycle-viewmodel-navigation3 = { module = "androidx.lifecycle:lifecycle-viewmodel-navigation3", version.ref = "androidx-viewmodel" } -
В файле
build.gradle.ktsмодуля KMP добавьте следующие зависимости в набор исходного кодаcommonMain:kotlin { // ... sourceSets { // ... commonMain.dependencies { implementation(libs.androidx.lifecycle.viewmodel.compose) implementation(libs.androidx.lifecycle.viewmodel.navigation3) } // ... } }
Если проект предназначен для настольной платформы, добавьте также зависимость kotlinx-coroutines-swing. При запуске корутин в ViewModel значение ViewModel.viewModelScope связано с Dispatchers.Main.immediate, которая может быть недоступна на настольных платформах по умолчанию. Библиотека Kotlinx Coroutines Swing обеспечивает корректную работу корутин ViewModel с Compose Multiplatform.
-
В каталоге версий Gradle:
[versions] kotlinx-coroutines = "1.10.2" [libraries] kotlinx-coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" } -
В файле
build.gradle.kts:kotlin { // ... sourceSets { // ... jvmMain.dependencies { implementation(libs.kotlinx.coroutines.swing) } // ... } }Подробности см. в документации по
Dispatchers.Main.
Использование ViewModel в общем коде
Compose Multiplatform предоставляет общую реализацию ViewModelStoreOwner, поэтому использование класса ViewModel в общем коде мало чем отличается от рекомендаций по разработке для Android.
Однако на платформах, отличных от JVM, есть важное отличие: рефлексия типов для создания объектов недоступна. В общем коде нельзя вызывать функцию viewModel() без параметров. При каждом создании экземпляра ViewModel необходимо передавать как минимум инициализатор в качестве аргумента.
Если указан только инициализатор, Compose Multiplatform автоматически создает фабрику по умолчанию. Однако вы можете реализовать собственные фабрики и вызывать более явные варианты общей функции viewModel(), как и в Jetpack Compose.
Определим ViewModel и подключим ее к компонуемой функции:
-
Определите простой класс
OrderViewModel, который управляет состоянием пользовательского интерфейса, включая количество и цену заказанного товара:data class OrderUiState(val quantity: Int = 0, val price: String = "$0.00") class OrderViewModel : ViewModel() { val uiState: StateFlow<OrderUiState> field = MutableStateFlow(OrderUiState()) fun setQuantity(n: Int) { uiState.update { it.copy(quantity = n, price = "$${n * 2}.00") } } } -
Добавьте пользовательскую ViewModel в компонуемую функцию, используя общую функцию
viewModel()с инициализатором:import com.example.ui.OrderViewModel @Composable fun CupcakeApp( viewModel: OrderViewModel = viewModel { OrderViewModel() }, ) { // ... }
Привязка ViewModel к областям действия в Navigation 3
При использовании ViewModel с Navigation 3 в общем коде по умолчанию ViewModel автоматически не привязываются к записям навигации. Без явной привязки каждая ViewModel будет связана с Activity, а не с экраном, даже после перехода пользователя на другой экран.
Чтобы привязать ViewModel и сохраняемое состояние Compose к каждой записи навигации, передайте декораторы записей Navigation 3 в NavDisplay при определении пунктов назначения навигации:
import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator
import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator
//...
NavDisplay(
entryDecorators = listOf(
// Saves Compose state per entry
rememberSaveableStateHolderNavEntryDecorator(),
// Scopes ViewModel per entry
rememberViewModelStoreNavEntryDecorator()
),
backStack = backStack,
entryProvider = entryProvider { }
)
ViewModel и внедрение зависимостей
Фреймворк для внедрения зависимостей (DI) позволяет внедрять в компоненты разные зависимости с учетом текущего окружения или целевой платформы. Для управления ViewModel можно использовать Koin, Metro или любой другой DI-фреймворк с поддержкой Kotlin Multiplatform.
Пример расширенного использования внедрения зависимостей см. в руководстве Совместное использование слоя доступа к данным.
Koin
Koin — это DI-фреймворк времени выполнения, который предоставляет DSL или аннотации для настройки зависимостей. Чтобы использовать Koin с Compose ViewModel, добавьте зависимость koin-compose-viewmodel.
Затем можно внедрить ViewModel в компонуемую функцию с помощью koinViewModel():
@Composable
fun CupcakeApp(
viewModel: UserViewModel = koinViewModel()
) {
// ...
}
Подробности см. в документации Koin о поддержке ViewModel и внедрении ViewModel в Compose.
Metro
Metro — это DI-фреймворк времени компиляции, реализованный в виде плагина компилятора Kotlin. Чтобы использовать Metro с Compose ViewModel, добавьте зависимость metrox-viewmodel-compose.
Затем можно внедрить ViewModel в компонуемую функцию с помощью metroViewModel():
@Composable
fun CupcakeApp(
viewModel: UserViewModel = metroViewModel()
) {
// ...
}
Подробности см. в документации MetroX о интеграции с ViewModel и доступе к ViewModel в Compose.
Уровни совместного использования кода
Вы можете выбрать, какие части кода сделать общими, а какие оставить специфичными для платформы:
Чтобы совместно использовать пользовательский интерфейс и бизнес-логику на разных платформах, см. руководство по совместному использованию логики и пользовательского интерфейса.
Чтобы совместно использовать часть кода без общей реализации пользовательского интерфейса, см. руководство по совместному использованию логики.
В следующих примерах показано, как использовать ViewModel при разных уровнях совместного использования кода. Все примеры основаны на описанном выше классе OrderViewModel.
Общие ViewModel и пользовательский интерфейс
В этом подходе все, включая ViewModel и пользовательский интерфейс, совместно используется благодаря Compose Multiplatform. Вы пишете код пользовательского интерфейса приложения один раз, и он будет работать на всех платформах.
@Composable
fun CupcakeApp(
viewModel: OrderViewModel = viewModel { OrderViewModel() }
) {
val uiState by viewModel.uiState.collectAsState()
Column(modifier = Modifier.padding(16.dp)) {
Text("Quantity: ${uiState.quantity}")
Text("Price: ${uiState.price}")
Button(onClick = { viewModel.setQuantity(6) }) {
Text("Set Quantity to '6'")
}
}
}
Общая ViewModel и специфичный для платформы пользовательский интерфейс
При таком подходе ViewModel (бизнес-логика) является общей, но на каждой платформе используется собственная реализация пользовательского интерфейса. Подробнее см. в разделе Настройка ViewModel для Kotlin Multiplatform.
Поскольку в этом случае пользовательский интерфейс не является общим, можно заменить версию библиотеки ViewModel для Compose Multiplatform на библиотеку androidx.lifecycle.
-
Обновите зависимости в каталоге версий Gradle:
[versions] androidx-viewmodel = "2.10.0" [libraries] androidx-lifecycle-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel", version.ref = "androidx-viewmodel" } -
В файле
build.gradle.ktsобъявите зависимость какapi, поскольку ее необходимо экспортировать в бинарный фреймворк:kotlin { // ... sourceSets { // ... commonMain.dependencies { api(libs.androidx.lifecycle.viewmodel) } // ... } }
Реализация для Android
На Android Jetpack Compose автоматически находит ViewModelStoreOwner, предоставленный Activity, и передает OrderViewModel.
@Composable
fun AndroidCupcakeApp(
viewModel: OrderViewModel = viewModel { OrderViewModel() }
) {
val uiState by viewModel.uiState.collectAsState()
Column {
Text("Quantity: ${uiState.quantity}")
Text("Price: ${uiState.price}")
Button(onClick = { viewModel.setQuantity(6) }) {
Text("Set Quantity to '6'")
}
}
}
Реализация для iOS
В iOS нет встроенного ViewModelStoreOwner, поэтому жизненный цикл ViewModel необходимо вручную связать со SwiftUI. Рекомендуем использовать библиотеку KMP-ObservableViewModel, которая позволяет SwiftUI напрямую наблюдать за ViewModel Kotlin Multiplatform и берет на себя необходимую для iOS стандартную настройку жизненного цикла ViewModel и владельца хранилища.
-
Экспортируйте API ViewModel, чтобы к нему можно было обращаться из Swift:
listOf( iosArm64(), iosSimulatorArm64(), ).forEach { it.binaries.framework { export(libs.androidx.lifecycle.viewmodel) baseName = "shared" } } -
Определите ViewModel в
commonMainс помощью базового класса ViewModel из KMP-ObservableViewModel и аннотации@NativeCoroutinesState:import com.rickclephas.kmp.observableviewmodel.ViewModel import com.rickclephas.kmp.nativecoroutines.NativeCoroutinesState import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow class OrderViewModel : ViewModel() { private val _uiState = MutableStateFlow(OrderUiState()) @NativeCoroutinesState val uiState: StateFlow<OrderUiState> = _uiState.asStateFlow() fun setQuantity(n: Int) { _uiState.value = _uiState.value.copy(quantity = n) } } -
Используйте ViewModel в точке входа пользовательского интерфейса iOS:
import SwiftUI import shared import KMPObservableViewModelSwiftUI @main struct iOSCupcakeApp: App { var body: some Scene { WindowGroup { CupcakeView() } } } struct CupcakeView: View { @StateViewModel private var viewModel = OrderViewModel() var body: some View { VStack { Text("Quantity: \(viewModel.uiState.quantity)") Text("Price: \(viewModel.uiState.price)") Button("Set Quantity to '6'") { viewModel.setQuantity(n: 6) } } } }
Общие слои репозитория и данных, специфичные для платформы ViewModel и пользовательский интерфейс
Еще один вариант — сделать общими только данные и слой репозитория, используя специфичные для платформы реализации ViewModel. Это позволяет применять на каждой платформе собственные подходы, например Hilt для внедрения зависимостей на Android или ObservableObject с Combine для iOS.
-
Создайте общий класс репозитория с логикой работы с данными:
class OrderRepository { fun calculatePrice(quantity: Int) = "$${quantity * 2}.00" } -
Реализуйте ViewModel для каждой платформы.
-
На Android используйте стандартную Android ViewModel и внедрите репозиторий:
class AndroidOrderViewModel( private val repo: OrderRepository ) : ViewModel() { val uiState: StateFlow<OrderUiState> field = MutableStateFlow(OrderUiState()) fun setQuantity(n: Int) { uiState.update { it.copy(quantity = n, price = repo.calculatePrice(n)) } } } -
На iOS реализуйте ViewModel средствами Swift с помощью
ObservableObject:import shared class IOSOrderViewModel: ObservableObject { private let repo: OrderRepository @Published var uiState: OrderUiState = OrderUiState() init(repo: OrderRepository) { self.repo = repo } func setQuantity(n: Int32) { uiState = OrderUiState(quantity: n, price: repo.calculatePrice(quantity: n)) } }
-
-
Реализуйте пользовательский интерфейс для каждой платформы.
-
На Android:
@Composable fun AndroidCupcakeApp( viewModel: AndroidOrderViewModel = viewModel { AndroidOrderViewModel(OrderRepository()) } ) { val uiState by viewModel.uiState.collectAsState() Column { Text("Quantity: ${uiState.quantity}") Text("Price: ${uiState.price}") Button(onClick = { viewModel.setQuantity(6) }) { Text("Set Quantity to '6'") } } } -
На iOS:
struct IOSCupcakeApp: App { @StateObject var viewModel = IOSOrderViewModel(repo: OrderRepository()) var body: some View { VStack { Text("Quantity: \(viewModel.uiState.quantity)") Text("Price: \(viewModel.uiState.price)") Button("Set Quantity to '6'") { viewModel.setQuantity(n: 6) } } } }
-
Что дальше
Ознакомьтесь с полным примером.
Дополнительные рекомендации для Android см. в разделе Настройка ViewModel для Kotlin Multiplatform.
Узнайте, как интегрировать Compose Multiplatform со SwiftUI при использовании общих ViewModel с нативным пользовательским интерфейсом.
© 2010–2026 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/multiplatform/compose-viewmodel.html