AndroidX ViewModel служит связующим звеном, устанавливая чёткое взаимодействие между общей бизнес-логикой и компонентами пользовательского интерфейса. Этот шаблон обеспечивает согласованность данных на разных платформах, позволяя настраивать пользовательский интерфейс под особенности каждой платформы. Вы можете продолжить разработку пользовательского интерфейса с помощью Jetpack Compose на Android и SwiftUI на iOS.
Подробнее о преимуществах использования ViewModel и всех возможностях читайте в основной документации по ViewModel .
Настройка зависимостей
Чтобы настроить KMP ViewModel в своем проекте, определите зависимость в файле libs.versions.toml
:
[versions]
androidx-viewmodel = 2.9.3
[libraries]
androidx-lifecycle-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel", version.ref = "androidx-viewmodel" }
Затем добавьте артефакт в файл build.gradle.kts
для вашего модуля KMP и объявите зависимость как api
, поскольку эта зависимость будет экспортирована в двоичный фреймворк:
// You need the "api" dependency declaration here if you want better access to the classes from Swift code.
commonMain.dependencies {
api(libs.androidx.lifecycle.viewmodel)
}
Экспорт API ViewModel для доступа из Swift
По умолчанию любая библиотека, добавляемая в кодовую базу, не будет автоматически экспортироваться в двоичный фреймворк. Если API не экспортированы, они доступны из двоичного фреймворка только при использовании их в общем коде (из исходного набора iosMain
или commonMain
). В этом случае API будут содержать префикс package, например, класс ViewModel
будет доступен как класс Lifecycle_viewmodelViewModel
. Подробнее об экспорте зависимостей в двоичные файлы см. в разделе «Зависимости экспорта».
Для улучшения опыта вы можете экспортировать зависимость ViewModel в двоичный фреймворк, используя настройку export
в файле build.gradle.kts
, где вы определяете двоичный фреймворк iOS, что делает API ViewModel доступными непосредственно из кода Swift так же, как и из кода Kotlin:
listOf(
iosX64(),
iosArm64(),
iosSimulatorArm64(),
).forEach {
it.binaries.framework {
// Add this line to all the targets you want to export this dependency
export(libs.androidx.lifecycle.viewmodel)
baseName = "shared"
}
}
(Необязательно) Использование viewModelScope
на JVM Desktop
При запуске сопрограмм в ViewModel свойство viewModelScope
привязывается к Dispatchers.Main.immediate
, которое может быть недоступно по умолчанию в десктопной версии. Для корректной работы добавьте в свой проект зависимость kotlinx-coroutines-swing
:
// Optional if you use JVM Desktop
desktopMain.dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-swing:[KotlinX Coroutines version]")
}
Более подробную информацию см. в документации Dispatchers.Main
.
Используйте ViewModel из commonMain
или androidMain
Нет особых требований к использованию класса ViewModel в общем commonMain
или в исходном наборе androidMain
. Единственное замечание — невозможность использования платформенно-зависимых API, и их необходимо абстрагировать. Например, если вы используете Application
Android в качестве параметра конструктора ViewModel, вам необходимо отказаться от этого API, абстрагировав его.
Более подробная информация об использовании платформенно-зависимого кода доступна в разделе Платформенно-зависимый код в Kotlin Multiplatform .
Например, в следующем фрагменте представлен класс ViewModel со своей фабрикой, определенной в commonMain
:
// commonMain/MainViewModel.kt class MainViewModel( private val repository: DataRepository, ) : ViewModel() { /* some logic */ } // ViewModelFactory that retrieves the data repository for your app. val mainViewModelFactory = viewModelFactory { initializer { MainViewModel(repository = getDataRepository()) } } fun getDataRepository(): DataRepository = DataRepository()
Затем в коде пользовательского интерфейса вы можете извлечь ViewModel обычным способом:
// androidApp/ui/MainScreen.kt @Composable fun MainScreen( viewModel: MainViewModel = viewModel( factory = mainViewModelFactory, ), ) { // observe the viewModel state }
Использовать ViewModel из SwiftUI
В Android жизненный цикл ViewModel автоматически обрабатывается и ограничивается ComponentActivity
, Fragment
, NavBackStackEntry
(навигация 2) или rememberViewModelStoreNavEntryDecorator
(навигация 3). Однако в SwiftUI для iOS нет встроенного аналога для AndroidX ViewModel.
Чтобы использовать ViewModel совместно с вашим приложением SwiftUI, вам необходимо добавить некоторый код настройки.
Создайте функцию, которая поможет с дженериками
Для создания экземпляра универсального ViewModel в Android используется функция отражения ссылки на класс . Поскольку универсальные типы Objective-C поддерживают не все функции Kotlin и Swift, невозможно напрямую получить ViewModel универсального типа из Swift.
Чтобы решить эту проблему, вы можете создать вспомогательную функцию, которая будет использовать ObjCClass
вместо универсального типа, а затем использовать getOriginalKotlinClass
для извлечения класса ViewModel для создания экземпляра:
// iosMain/ViewModelResolver.ios.kt /** * This function allows retrieving any ViewModel from Swift Code with generics. We only get * [ObjCClass] type for the [modelClass], because the interop between Kotlin and Swift code * doesn't preserve the generic class, but we can retrieve the original KClass in Kotlin. */ @BetaInteropApi @Throws(IllegalArgumentException::class) fun ViewModelStore.resolveViewModel( modelClass: ObjCClass, factory: ViewModelProvider.Factory, key: String?, extras: CreationExtras? = null, ): ViewModel { @Suppress("UNCHECKED_CAST") val vmClass = getOriginalKotlinClass(modelClass) as? KClass<ViewModel> require(vmClass != null) { "The modelClass parameter must be a ViewModel type." } val provider = ViewModelProvider.Companion.create(this, factory, extras ?: CreationExtras.Empty) return key?.let { provider[key, vmClass] } ?: provider[vmClass] }
Затем, когда вы захотите вызвать функцию из Swift, вы можете написать универсальную функцию типа T : ViewModel
и использовать T.self
, которая может передать ObjCClass
в функцию resolveViewModel
.
Подключите область ViewModel к жизненному циклу SwiftUI
Следующий шаг — создание класса IosViewModelStoreOwner
, реализующего интерфейсы (протоколы) ObservableObject
и ViewModelStoreOwner
. ObservableObject
нужен для возможности использования этого класса в качестве @StateObject
в коде SwiftUI:
// iosApp/IosViewModelStoreOwner.swift class IosViewModelStoreOwner: ObservableObject, ViewModelStoreOwner { let viewModelStore = ViewModelStore() /// This function allows retrieving the androidx ViewModel from the store. /// It uses the utilify function to pass the generic type T to shared code func viewModel<T: ViewModel>( key: String? = nil, factory: ViewModelProviderFactory, extras: CreationExtras? = nil ) -> T { do { return try viewModelStore.resolveViewModel( modelClass: T.self, factory: factory, key: key, extras: extras ) as! T } catch { fatalError("Failed to create ViewModel of type \(T.self)") } } /// This is called when this class is used as a `@StateObject` deinit { viewModelStore.clear() } }
Этот владелец позволяет получать несколько типов ViewModel, аналогично Android. Жизненный цикл этих ViewModel завершается, когда экран, использующий IosViewModelStoreOwner
, деинициализируется и вызывает deinit
. Подробнее о деинициализации можно узнать в официальной документации .
На этом этапе вы можете просто создать экземпляр IosViewModelStoreOwner
как @StateObject
в представлении SwiftUI и вызвать функцию viewModel
для получения ViewModel:
// iosApp/ContentView.swift struct ContentView: View { /// Use the store owner as a StateObject to allow retrieving ViewModels and scoping it to this screen. @StateObject private var viewModelStoreOwner = IosViewModelStoreOwner() var body: some View { /// Retrieves the `MainViewModel` instance using the `viewModelStoreOwner`. /// The `MainViewModel.Factory` and `creationExtras` are provided to enable dependency injection /// and proper initialization of the ViewModel with its required `AppContainer`. let mainViewModel: MainViewModel = viewModelStoreOwner.viewModel( factory: MainViewModelKt.mainViewModelFactory ) // ... // .. the rest of the SwiftUI code } }
Недоступно в Kotlin Multiplatform
Некоторые API, доступные в Android, недоступны в Kotlin Multiplatform.
Интеграция с Hilt
Поскольку Hilt недоступен для проектов Kotlin Multiplatform, вы не можете напрямую использовать ViewModel с аннотацией @HiltViewModel
в commonMain
sourceSet. В этом случае вам потребуется альтернативный фреймворк DI, например, Koin , kotlin-inject , Metro или Kodein . Все фреймворки DI, работающие с Kotlin Multiplatform, можно найти на сайте klibs.io .
Наблюдайте за потоками в SwiftUI
Наблюдение за потоками сопрограмм в SwiftUI напрямую не поддерживается. Однако вы можете использовать библиотеку KMP-NativeCoroutines или SKIE для реализации этой функции.