Spec-Zone.ru › Kotlin 2

Мультиплатформенная ViewModel

Android ViewModel позволяет связать бизнес-логику приложения с компонентами пользовательского интерфейса. С Compose Multiplatform вы также можете использовать ViewModel в общем коде.

На этой странице описано, как настроить и использовать ViewModel в мультиплатформенном проекте:

  • Настройте зависимости.

  • Используйте ViewModel в общем коде.

  • Привяжите ViewModel к пунктам назначения навигации.

  • Внедряйте зависимости с помощью Koin или Metro.

  • Выберите, какую часть кода ViewModel и пользовательского интерфейса сделать общей: от полностью общего подхода до совместного использования только репозитория или слоя данных.

Настройка зависимостей

Чтобы сделать ViewModel и пользовательский интерфейс общими для разных платформ:

  1. Определите зависимости в файле каталога версий 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" }
    

    Следить за изменениями реализации ViewModel для мультиплатформенных проектов можно в разделе Что нового или отслеживая выпуски EAP в журнале изменений Compose Multiplatform.

  2. В файле 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.

  1. В каталоге версий Gradle:

    [versions]
    kotlinx-coroutines = "1.10.2"
    
    [libraries]
    kotlinx-coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" }
    
  2. В файле 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 и подключим ее к компонуемой функции:

  1. Определите простой класс 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") }
       }
    }
    

    В этом примере используются явные поля хранения, стабилизированные в Kotlin 2.4.0. В более ранних версиях добавьте параметр компилятора -Xexplicit-backing-fields или используйте прежний шаблон с полями хранения и .asStateFlow().

  2. Добавьте пользовательскую 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.

  1. Обновите зависимости в каталоге версий Gradle:

    [versions]
    androidx-viewmodel = "2.10.0"
    
    [libraries]
    androidx-lifecycle-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel", version.ref = "androidx-viewmodel" }
    
  2. В файле 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 и владельца хранилища.

  1. Экспортируйте API ViewModel, чтобы к нему можно было обращаться из Swift:

    listOf(
       iosArm64(),
       iosSimulatorArm64(),
    ).forEach {
       it.binaries.framework {
          export(libs.androidx.lifecycle.viewmodel)
          baseName = "shared"
       }
    }
    
  2. Определите 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)
         }
     }
    
  3. Используйте 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.

  1. Создайте общий класс репозитория с логикой работы с данными:

    class OrderRepository {
       fun calculatePrice(quantity: Int) = "$${quantity * 2}.00"
    }
    
  2. Реализуйте 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))
         }
      }
      
  3. Реализуйте пользовательский интерфейс для каждой платформы.

    • На 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 с нативным пользовательским интерфейсом.

20 июля 2026 г.
Жизненный циклНавигация в Compose

© 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

Spec-Zone.ru

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