Compose における ViewModel
Koin は、Compose アプリケーションで ViewModel を注入(inject)するためのいくつかの API を提供しています。このガイドでは、すべての ViewModel 注入パターンについて説明します。
INFO
モジュールでの ViewModel の宣言については、Core ViewModel を参照してください。このページでは、Compose での ViewModel の取得に焦点を当てています。
セットアップ
// Compose Multiplatform (または Android)
implementation("io.insert-koin:koin-compose-viewmodel:$koin_version")
// Android 向けの便利なパッケージ (koin-compose + koin-compose-viewmodel を含む)
implementation("io.insert-koin:koin-androidx-compose:$koin_version")
// Navigation 連携を使用する場合
implementation("io.insert-koin:koin-compose-viewmodel-navigation:$koin_version")INFO
すべての ViewModel API は koin-compose-viewmodel に含まれています。koin-androidx-compose パッケージにはこれが自動的に含まれます。
ViewModel の宣言
コンパイラプラグイン DSL
class UserViewModel(
private val repository: UserRepository
) : ViewModel()
val appModule = module {
viewModel<UserViewModel>()
}アノテーション
@KoinViewModel
class UserViewModel(
private val repository: UserRepository
) : ViewModel()クラシック DSL
val appModule = module {
viewModelOf(::UserViewModel)
// またはラムダを使用
viewModel { UserViewModel(get()) }
}ViewModel 注入 API
koinViewModel() - 基本的な注入
Compose で ViewModel を注入するための主要な API です。
@Composable
fun UserScreen() {
val viewModel = koinViewModel<UserViewModel>()
// viewModel を使用...
}ベストプラクティス - テスト容易性(Testability)のためにデフォルトパラメータとして注入します:
@Composable
fun UserScreen(
viewModel: UserViewModel = koinViewModel()
) {
val state by viewModel.state.collectAsState()
// UI...
}koinNavViewModel() - Navigation 引数を使用する場合
Navigation Compose を使用する場合、koinNavViewModel() を使用すると、SavedStateHandle を介してナビゲーション引数を自動的に受け取ることができます。
// 引数付きのルート
NavHost(navController, startDestination = "list") {
composable("detail/{itemId}") { backStackEntry ->
DetailScreen()
}
}
// ViewModel は自動的に引数を受け取る
class DetailViewModel(
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
val itemId: String = savedStateHandle["itemId"] ?: ""
}
@Composable
fun DetailScreen(
viewModel: DetailViewModel = koinNavViewModel()
) {
// viewModel.itemId にはナビゲーション引数の値が入っている
}koinActivityViewModel() - Activity スコープ (Android)
同じ Activity 内のすべての Composable で ViewModel を共有します。
@Composable
fun ScreenA() {
// Activity 全体で同じインスタンス
val sharedVM = koinActivityViewModel<SharedViewModel>()
}
@Composable
fun ScreenB() {
// ScreenA と同じインスタンス
val sharedVM = koinActivityViewModel<SharedViewModel>()
}NOTE
バージョン 4.1 以降の koin-androidx-compose で利用可能です。
sharedKoinViewModel() - ナビゲーショングラフスコープ
ナビゲーショングラフ内で ViewModel を共有します(実験的機能)。
navigation<Route.BookGraph>(startDestination = Route.BookList) {
composable<Route.BookList> { backStackEntry ->
val sharedVM = backStackEntry.sharedKoinViewModel<BookSharedViewModel>(navController)
BookListScreen(sharedVM)
}
composable<Route.BookDetail> { backStackEntry ->
// BookGraph 内で同じインスタンス
val sharedVM = backStackEntry.sharedKoinViewModel<BookSharedViewModel>(navController)
BookDetailScreen(sharedVM)
}
}パラメータ付きの ViewModel
@InjectedParam の使用
ランタイムパラメータに @InjectedParam を付けます。
class DetailViewModel(
@InjectedParam val itemId: String,
private val repository: DetailRepository
) : ViewModel()
// コンパイラプラグイン DSL
val appModule = module {
viewModel<DetailViewModel>()
}パラメータを指定して注入します:
@Composable
fun DetailScreen(itemId: String) {
val viewModel = koinViewModel<DetailViewModel> {
parametersOf(itemId)
}
}クラシック DSL でのパラメータ指定
val appModule = module {
viewModel { params ->
DetailViewModel(
itemId = params.get(),
repository = get()
)
}
}SavedStateHandle
Koin は SavedStateHandle を自動的に ViewModel に提供します。
@KoinViewModel
class MyViewModel(
private val handle: SavedStateHandle,
private val repository: UserRepository
) : ViewModel() {
// ナビゲーション引数にアクセス
val userId: String? = handle["userId"]
// プロセスの終了(Process death)をまたいで状態を保持する
var query by handle.saveable { mutableStateOf("") }
}val appModule = module {
viewModel<MyViewModel>() // SavedStateHandle は自動的に注入される
}INFO
SavedStateHandle は、コンテキストに応じて ViewModel の CreationExtras または Navigation の BackStackEntry から注入されます。
ViewModel スコープ
viewModelScope を使用して、依存関係を ViewModel のライフサイクルにスコープします。
コンパイラプラグイン DSL
val appModule = module {
viewModelScope {
scoped<UserCache>()
scoped<UserRepository>()
viewModel<UserViewModel>()
}
}アノテーション
@ViewModelScope
class UserCache
@ViewModelScope
class UserRepository(private val cache: UserCache)
@KoinViewModel
@ViewModelScope
class UserViewModel(private val repository: UserRepository) : ViewModel()クラシック DSL
val appModule = module {
viewModelScope {
scoped { UserCache() }
scoped { UserRepository(get()) }
viewModel { UserViewModel(get()) }
}
}クイックリファレンス
| API | ユースケース | パッケージ |
|---|---|---|
koinViewModel() | 基本的な ViewModel 注入 | koin-compose-viewmodel |
koinNavViewModel() | Navigation 引数を使用する場合 | koin-compose-viewmodel-navigation |
koinActivityViewModel() | Activity 全体で共有 (Android) | koin-androidx-compose |
sharedKoinViewModel() | ナビゲーショングラフ内で共有 | koin-compose-viewmodel-navigation |
ベストプラクティス
デフォルトパラメータとして注入する - Koin なしでのテストが可能になります。
kotlin@Composable fun MyScreen(viewModel: MyViewModel = koinViewModel())Navigation では koinNavViewModel() を使用する - 引数の処理が自動化されます。
ViewModel 固有の依存関係には viewModelScope を優先する - クリーンなライフサイクル管理が可能になります。
コールバック内で ViewModel を注入しない - Composable レベルで注入してください。
kotlin// 非推奨 (Bad) Button(onClick = { val vm = koinViewModel<MyVM>() }) // 推奨 (Good) val vm = koinViewModel<MyVM>() Button(onClick = { vm.doSomething() })
次のステップ
- Compose ライフサイクル - 状態と再コンポジション
- Core ViewModel - ViewModel 宣言 DSL
- Android ViewModel - Android 固有の機能
