20
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ZOZOAdvent Calendar 2023

Day 1

Jetpack ComposeでViewModelに任意のパラメータを渡したい

Last updated at Posted at 2023-11-30

この記事はZOZO Advent Calendar 2023 シリーズ 3の1日目の記事です。

概要

Android Architecture ComponentsのViewModelを利用する機会は多いかと思います。Hiltと一緒に使用すると次のようになります。

@HiltViewModel
class SampleViewModel @Inject constructor() : ViewModel() {
    // ViewModelの実装
}

この時、次のようにコンストラクタにパラメータを持たせ、ViewModelのインスタンス作成時に任意の値を与えたい場合が往々としてあります。

@HiltViewModel
class SampleViewModel @Inject constructor(
    val name: String,
) : ViewModel() {
    // ViewModelの実装
}

InitializerViewModelFactoryによるパラメータの受け渡し

ViewModelProvider.Factoryを実装する方法がありますが面倒です。InitializerViewModelFactoryを使うと簡単に書けます。viewModelFactoryinitializerといったDSLでViewModelFactoryを構築することが可能です。

@Composable
fun SampleScreen() {
    val viewModel: SampleViewModel = viewModel(
        factory = viewModelFactory {
            initializer {
                SampleViewModel(
                    name = "horie1024",
                )
            }
        },
    )
}

さらに、viewModelはinitializerを受け取れるので、より簡潔に書けます。

@Composable
fun SampleScreen() {
    val viewModel: SampleViewModel = viewModel {
        SampleViewModel(
            name = "horie1024",
        )
    }
}

HiltによるDIとの共存

ViewModelが例えばSampleRepositoryに依存している場合を考えます。

@HiltViewModel
class SampleViewModel @Inject constructor(
    val name: String,
    val repository: SampleRepository
) : ViewModel() {
    // ViewModelの実装
}

SampleRepositoryは次のような形で、SampleViewModelにはSampleRepositoryの実装SampleRepositoryImplがHiltによってDIされます。

interface SampleRepository {}
class SampleRepositoryImpl @Inject constructor(): SampleRepository {}

この場合にInitializerViewModelFactoryでSampleViewModelのインスタンスを作ろうとすると、SampleRepositoryImplのインスタンスを自分で用意する必要があります。

@Composable
fun SampleScreen() {
    val viewModel: SampleViewModel = viewModel {
        SampleViewModel(
            name = "horie1024",
            repository = SampleRepositoryImpl()
        )
    }
}

nameには自分で任意の値を渡したいですが、SampleRepositoryのインスタンスはHitlに用意してもらえると嬉しいです。

SavedStateHandleでのパラメータの受け渡し

AssitedInjectを利用する方法もあるのですが、今回はSavedStateHandleを介してViewModelにパラメータを渡すことにしました。

viewModelでViewModelのインスタンスを作成する際にextrasを指定できるのでここでMutableCreationExtrasを使い指定したパラメータをセットします。MutableCreationExtrasにはdefaultViewModelCreationExtrasを渡します。

viewModel Composableの実装でもdefaultViewModelCreationExtrasを取得してextrasにセットしています。

@Composable
fun SampleScreen() {
    val owner = LocalViewModelStoreOwner.current
    val defaultViewModelCreationExtras =
        (owner as? HasDefaultViewModelProviderFactory)?.defaultViewModelCreationExtras
            ?: CreationExtras.Empty
    val viewModel: SampleViewModel = viewModel(
        extras = MutableCreationExtras(defaultViewModelCreationExtras).apply {
            set(DEFAULT_ARGS_KEY, bundleOf(NAME_KEY to "horie1024"))
        },
    )
}

これで次のようにsavedStateHandleから値を取得できます。

@HiltViewModel
class SampleViewModel @Inject constructor(
    savedStateHandle: SavedStateHandle,
    val repository: SampleRepository
) : ViewModel() {
    
    val name = savedStateHandle.get<String>(NAME_KEY) 

    // ViewModelの実装

    companion object {
        const val NAME_KEY = "name"
    }
}

HiltViewModelExtensions.withCreationCallback

Daggerの次のリリースで導入されるようですが、@HiltViewMmodel@AssistedInjectを統合して利用できるようになるそうです1

SampleViewModelを例にすると実装は次のようになります。 ComposeからもhiltViewModelを使って利用できるようになりそうなので楽しみです。

2024/2/6追記: Composeで hiltViewModel からの利用もできるようになりました https://qiita.com/nkshigeru/items/54a07ba1ce03bf24716f

@HiltViewModel(assistedFactory = SampleViewModelFactory::class)
class SampleViewModel @AssistedInject constructor(
    @Assisted val name: String,
    val repository: SampleRepository
) : ViewModel() {
    // ViewModelの実装
}

@AssistedFactory
interface SampleViewModelFactory {
  fun create(val name: String): SampleViewModel
}

@AndroidEntryPoint
class SampleActivity : AppCompatActivity() {
  private val name = "horie1024"
  private val sampleViewModel by viewModels<SampleViewModel>(
    extrasProducer = {
      defaultViewModelCreationExtras.withCreationCallback<
          SampleViewModelFactory> { factory ->
              factory.create(name)
          }
    }
  )
}

まとめ

簡単にですがJetpack ComposeでViewModelに任意のパラメータを渡す方法についてまとめました。

明日のAdvent Calendarの担当は @ssssota さんです。どんな記事が投稿されるのか楽しみです。

参考リンク

  1. 12/2追記: リリースされました。https://github.com/google/dagger/releases/tag/dagger-2.49

20
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?