🛒

[Android] Compose で ViewModel に引数を渡す方法(2022~)

2023/07/02に公開

前書き

  • タイトルの方法をググっていたらサイトにより書き方に細かい差異があったのでまとめたものです

やりたいこと

ViewModel にリポジトリなどの引数を渡したい。

通常(?)の引数なしの場合は以下の様に書く

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewmodel.compose.viewModel

class HomeViewModel: ViewModel() {

}

@Composable
fun HomeScreen(viewModel: HomeViewModel = viewModel()) {

}

引数を渡したくても HomeScreen(HomeViewModel(repository)) みたいな書き方はできないので、どうするの?という話になる。

方法1: ViewModelProvider.Factory

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)
}

方法2: ViewModelProvider.NewInstanceFactory

NewInstanceFactoryViewModelProvider.Factory の実装であり、公式のドキュメントでは「与えられたクラスの空のコンストラクタを呼ぶシンプルなクラス」と説明されています。

ViewModelProvider.Factory と同様に fun <T : ViewModel> create(modelClass: Class<T>): T をオーバーライドすれば目的を果たせますが、説明と食い違ってしまうので今回のケースではあえて使う理由はなさそうです。

方法3: viewModelFactory

viewModelFactoryandroidx.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)
}

後書き

  • 便利になってるのはいいことですが、どう書くのがいいの?で迷ってしまうのは後追いの大変なところですね。

参考資料

Discussion