AndroidX ViewModel служит мостом, устанавливающим четкий контракт между вашей общей бизнес-логикой и компонентами пользовательского интерфейса. Такой подход обеспечивает согласованность данных на разных платформах и позволяет настраивать пользовательские интерфейсы в соответствии с особенностями каждой платформы. Вы можете продолжить разработку пользовательского интерфейса с помощью Jetpack Compose на Android и SwiftUI на iOS.
Подробнее о преимуществах использования ViewModel и всех функциях читайте в основной документации по ViewModel.
Настройте зависимости
Чтобы настроить KMP ViewModel в проекте, определите зависимость в файле libs.versions.toml:
[versions]
androidx-viewmodel = 2.11.0
[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 будут содержать префикс пакета, например класс 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, предназначенные для конкретной платформы, и вам нужно их абстрагировать. Например, если вы используете Android Application в качестве параметра конструктора 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 (Navigation 2) или rememberViewModelStoreNavEntryDecorator (Navigation 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, вы не можете напрямую использовать ViewModels с аннотацией @HiltViewModel в commonMain sourceSet. В этом случае вам нужно использовать альтернативный фреймворк DI, например Koin, kotlin-inject, Metro или Kodein. Все фреймворки DI, которые работают с Kotlin Multiplatform, можно найти на сайте klibs.io.
Отслеживание потоков в SwiftUI
Наблюдение за потоками сопрограмм в SwiftUI напрямую не поддерживается. Однако вы можете использовать библиотеку KMP-NativeCoroutines или SKIE, чтобы включить эту функцию.