AndroidX ViewModel は、共有ビジネス ロジックと UI コンポーネントの間に明確な契約を確立するブリッジとして機能します。このパターンは、プラットフォーム間でデータの一貫性を確保しながら、各プラットフォームの固有の外観に合わせて UI をカスタマイズできるようにします。UI の開発は、Android では Jetpack Compose、iOS では SwiftUI を使用して引き続き行うことができます。
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" }
次に、KMP モジュールの build.gradle.kts ファイルにアーティファクトを追加し、依存関係を 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)
}
Swift からアクセスできるように ViewModel API をエクスポートする
デフォルトでは、コードベースに追加したライブラリはバイナリ フレームワークに自動的にエクスポートされません。API がエクスポートされていない場合、共有コード(iosMain または commonMain ソースセット)で使用している場合にのみ、バイナリ フレームワークから API を利用できます。その場合、API にはパッケージ プレフィックスが含まれます。たとえば、ViewModel クラスは Lifecycle_viewmodelViewModel クラスとして使用できます。依存関係のエクスポートの詳細については、依存関係をバイナリにエクスポートするをご覧ください。
エクスペリエンスを改善するには、iOS バイナリ フレームワークを定義する build.gradle.kts ファイルの export 設定を使用して、ViewModel 依存関係をバイナリ フレームワークにエクスポートします。これにより、Kotlin コードと同様に、Swift コードから ViewModel API に直接アクセスできるようになります。
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"
}
}
(省略可)JVM デスクトップで viewModelScope を使用する
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 のドキュメントをご覧ください。
commonMain または androidMain の ViewModel を使用する
共有 commonMain で ViewModel クラスを使用するための特別な要件はありません。androidMain sourceSet からも同様です。唯一の考慮事項は、プラットフォーム固有の API を使用できないため、それらを抽象化する必要があることです。たとえば、Android Application を ViewModel コンストラクタ パラメータとして使用している場合は、この API を抽象化して移行する必要があります。
プラットフォーム固有のコードの使用方法について詳しくは、Kotlin マルチプラットフォームのプラットフォーム固有のコードをご覧ください。
たとえば、次のスニペットは、commonMain で定義されたファクトリを持つ ViewModel クラスです。
// 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()
次に、UI コードで、通常どおり ViewModel を取得します。
// androidApp/ui/MainScreen.kt @Composable fun MainScreen( viewModel: MainViewModel = viewModel( factory = mainViewModelFactory, ), ) { // observe the viewModel state }
SwiftUI から ViewModel を使用する
Android では、ViewModel のライフサイクルは自動的に処理され、ComponentActivity、Fragment、NavBackStackEntry(Navigation 2)、rememberViewModelStoreNavEntryDecorator(Navigation 3)にスコープされます。ただし、iOS の SwiftUI には、AndroidX ViewModel に相当する組み込み機能はありません。
ViewModel を SwiftUI アプリと共有するには、セットアップ コードを追加する必要があります。
ジェネリックをサポートする関数を作成する
汎用 ViewModel インスタンスをインスタンス化するには、Android のクラス参照リフレクション機能を使用します。Objective-C のジェネリックは、Kotlin または Swift のすべての機能をサポートしているわけではないため、Swift からジェネリック型の ViewModel を直接取得することはできません。
この問題に対処するには、ジェネリクス型の代わりに 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 のライフサイクルに接続する
次のステップでは、ObservableObject インターフェース(プロトコル)と ViewModelStoreOwner インターフェース(プロトコル)を実装する IosViewModelStoreOwner を作成します。ObservableObject を使用する理由は、このクラスを SwiftUI コードで @StateObject として使用できるようにするためです。
// 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() } }
このオーナーを使用すると、Android と同様に、複数の ViewModel タイプを取得できます。これらの ViewModel のライフサイクルは、IosViewModelStoreOwner を使用する画面が初期化解除され、deinit を呼び出すとクリアされます。初期化解除について詳しくは、公式ドキュメントをご覧ください。
この時点で、SwiftUI ビューで IosViewModelStoreOwner を @StateObject としてインスタンス化し、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 では利用不可
Android で利用可能な API の一部は、Kotlin マルチプラットフォームでは利用できません。
Hilt との統合
Hilt は Kotlin マルチプラットフォーム プロジェクトでは使用できないため、commonMain sourceSet で @HiltViewModel アノテーションを含む ViewModel を直接使用することはできません。その場合は、Koin、kotlin-inject、Metro、Kodein などの代替 DI フレームワークを使用する必要があります。Kotlin Multiplatform で動作するすべての DI フレームワークについては、klibs.io をご覧ください。
SwiftUI で Flow を監視する
SwiftUI でのコルーチン Flow のモニタリングは直接サポートされていません。ただし、KMP-NativeCoroutines ライブラリまたは SKIE ライブラリを使用すると、この機能を利用できます。