AndroidX ViewModel は、共有ビジネス ロジックと UI コンポーネントの間に明確なコントラクトを確立する橋渡し役として機能します。このパターンは、プラットフォーム間でデータの一貫性を確保し、各プラットフォームの異なる外観に合わせて UI をカスタマイズできるようにします。Android では Jetpack Compose、iOS では SwiftUI を使用して、引き続き UI を開発できます。
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" }
次に、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 にはパッケージ接頭辞が含まれます。たとえば、ViewModel
クラスは Lifecycle_viewmodelViewModel
クラスとして使用できます。依存関係のエクスポートの詳細については、依存関係をバイナリにエクスポートするをご覧ください。
エクスペリエンスを向上させるには、iOS バイナリ フレームワークを定義する build.gradle.kts
ファイルの export
設定を使用して、ViewModel の依存関係をバイナリ フレームワークにエクスポートします。これにより、ViewModel API に 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"
}
}
(省略可)JVM Desktop で 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 を使用できず、抽象化する必要があることです。たとえば、ViewModel コンストラクタ パラメータとして Android Application
を使用している場合は、この 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 View で 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 マルチプラットフォームでは利用不可
Android で利用できる API の一部は、Kotlin Multiplatform では利用できません。
Hilt との統合
Kotlin マルチプラットフォーム プロジェクトでは Hilt を使用できないため、commonMain
sourceSet で @HiltViewModel
アノテーション付きの ViewModel を直接使用することはできません。その場合は、Koin、kotlin-inject、Metro、Kodein などの別の DI フレームワークを使用する必要があります。Kotlin Multiplatform で動作するすべての DI フレームワークは、klibs.io で確認できます。
SwiftUI でフローを監視する
SwiftUI でコルーチンの Flow を直接監視することはできません。ただし、KMP-NativeCoroutines ライブラリまたは SKIE ライブラリを使用すると、この機能を有効にできます。