[Android] Compose で ViewModel に引数を渡す方法(2022~)
前書き
- タイトルの方法をググっていたらサイトにより書き方に細かい差異があったのでまとめたものです
やりたいこと
ViewModel にリポジトリなどの引数を渡したい。
通常(?)の引数なしの場合は以下の様に書く
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewmodel.compose.viewModel
class HomeViewModel: ViewModel() {
}
@Composable
fun HomeScreen(viewModel: HomeViewModel = viewModel()) {
}
引数を渡したくても HomeScreen(HomeViewModel(repository))
みたいな書き方はできないので、どうするの?という話になる。
ViewModelProvider.Factory
方法1: ViewModelProvider.Factory
オブジェクトを実装して、 viewModel()
の factory
引数で渡す。
import androidx.lifecycle.ViewModelProvider
class HomeViewModelFactory(private val repository: Repository) :ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
@Suppress("UNCHECKED_CAST")
return HomeViewModel(repository) as T
}
}
// 親コンポーザブルで ViewModel を生成
@Composable
fun Container() {
val repository = Repository()
val homeViewModel :HomeViewModel = viewModel(
factory = HomeViewModelFactory(repository)
)
HomeScreen(homeViewModel)
}
上では仕組みの理解のために HomeViewModelFactory クラスを実装しているが、より実践的には ViewModel の companion object で Factory object を返すメソッドを実装する。
class HomeViewModel(): ViewModel() {
// ...
companion object {
fun provideFactory(repository: Repository) = object : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
@Suppress("UNCHECKED_CAST")
return HomeViewModel(repository) as T
}
}
}
}
@Composable
Container() {
val repository = Repository()
val homeViewModel :HomeViewModel = viewModel(
factory = HomeViewModel.provideFactory(repository)
)
HomeScreen(homeViewModel)
}
ViewModelProvider.NewInstanceFactory
方法2: NewInstanceFactory
は ViewModelProvider.Factory
の実装であり、公式のドキュメントでは「与えられたクラスの空のコンストラクタを呼ぶシンプルなクラス」と説明されています。
ViewModelProvider.Factory
と同様に fun <T : ViewModel> create(modelClass: Class<T>): T
をオーバーライドすれば目的を果たせますが、説明と食い違ってしまうので今回のケースではあえて使う理由はなさそうです。
viewModelFactory
方法3: viewModelFactory
は androidx.lifecycle:lifecycle-viewmodel:2.5.0
(2022/06/29) で正式に追加された Kotlin DSL です。これにより factory オブジェクトをより簡潔に生成できるようになりました。
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
class HomeViewModel: ViewModel() {
// ...
companion object {
fun provideFactory(repository: Repository) = viewModelFactory {
initializer {
HomeViewModel(repository)
}
}
}
}
- (正式に: 厳密には 2.5.0-alpha03 から追加されているがアルファ版なのでの意味)
方法4: viewModel にラムダを渡す
androidx.lifecycle:lifecycle-viewmodel-compose:2.5.0
で正式に viewModel()
がラムダファクトリを受け取れるようになったため、 カスタムの ViewModelProvider.Factory
を作成する必要がなくなりました。
@Composable
fun Container() {
val repository = Repository()
val homeViewModel = viewModel {
HomeViewModel(repository)
}
HomeScreen(homeViewModel)
}
今時点でどうやって書くか
ViewModelProvider.Factory
も知識としては持っておいた方がよいですが、実際に書く時は方法4 のラムダファクトリを使うか、ファクトリ自体が複雑になりそうな場合は ViewModelFactory
を利用するのがよさそうです。ここでは説明を省略しますが、 ViewModelFactory
では、複数の ViewModel を生成する(一元管理する)ファクトリも作成できます。
おまけ( CreationExtra )
公式のドキュメントままですが、CreationExtra オブジェクトからアプリケーションを取得できるので、独自のアプリケーションオブジェクトの属性としてリポジトリやリポジトリのコンテナを生やしてそれを ViewModel に渡すという流れがやりやすくなっています。
val homeViewModel = viewModel { // this: CreationExtras
val application = get(ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY) as MyApplication
HomeViewModel(application.repository)
}
後書き
- 便利になってるのはいいことですが、どう書くのがいいの?で迷ってしまうのは後追いの大変なところですね。
参考資料
- https://developer.android.com/jetpack/androidx/releases/lifecycle?hl=ja
- https://developer.android.com/codelabs/basic-android-kotlin-compose-add-repository?hl=ja#5
- https://developer.android.com/reference/androidx/lifecycle/ViewModelProvider.NewInstanceFactory
- https://star-zero.medium.com/initializerviewmodelfactoryを使ってviewmodelを生成-25f46c6cb4e1
Discussion