Closed4
【Jetpack Compose】Hilt を利用してインスタンス化する ViewModel に依存している場合、どのように Preview を機能させればいいのか
【当スクラップの疑問点】
Composable 関数が ViewModel に依存している。
この時、Jetpack Compose の Preview 機能が利用できなくなる。
【問題となるコードの例】
問題を起こすためのサンプルコードなのでやってる事がショボいのは許して欲しい。
(Hilt 導入済み前提である。)
@HiltViewModel
class MyScreenViewModel @Inject constructor(private val myCalculate: MyCalculate) : ViewModel() {
private val _count = MutableStateFlow(0)
val count = _count.asStateFlow()
fun increment() {
_count.value = myCalculate.increment(_count.value)
}
}
class MyCalculate @Inject constructor() {
fun increment(n: Int): Int = n + 1
}
@Composable
fun MyScreen(viewModel: MyScreenViewModel = viewModel()) {
val count by viewModel.count.collectAsState()
Column {
Text(text = count.toString())
Button(onClick = { viewModel.increment() }) {
}
}
}
@Preview(showBackground = true)
@Composable
private fun MyScreenPreview() {
MyScreen()
}
【解決策】
・ViewModel を抽象に依存させ、Preview 側では Preview 用の ViewModel を使う。
・ViewModel をそもそも使わない。
などが考えられると思う。
【他の解決策】
React などで使われている Container/Presentational Pattern を利用する。
@HiltViewModel
class MyScreenViewModel @Inject constructor(private val myCalculate: MyCalculate) : ViewModel() {
private val _count = MutableStateFlow(0)
val count = _count.asStateFlow()
fun increment() {
_count.value = myCalculate.increment(_count.value)
}
}
class MyCalculate @Inject constructor() {
fun increment(n: Int): Int = n + 1
}
// 先ほどのコードから UI を分離した。
// 役割としては、
// 1. State の保持をする。
// 2. UI へ State を引数で渡す、ViewModel のメソッドを呼び出せるように渡す。
// アプリケーションで使うときは MyScreenContent ではなく、こちらを呼び出す。
@Composable
fun MyScreen(viewModel: MyScreenViewModel = viewModel()) {
val count by viewModel.count.collectAsState()
MyScreenContent(count = count.toString()) { viewModel.increment() }
}
// こちらで UI の描画を行うように分離した。
// これによって Stateless になる。
// Preview ではこちらを使う。
@Composable
private fun MyScreenContent(count: String, onClick: () -> Unit) {
Column {
Text(text = count)
Button(onClick = { onClick() }) {
}
}
}
// State の保持と UI の表示の役割を分離したことによって Preview が ViewModel に依存しなくなった。
// なので引数として Preview 用の処理を渡す。
@Preview(showBackground = true)
@Composable
private fun MyScreenPreview() {
val count = remember { mutableStateOf(0) }
MyScreenContent(count.value.toString()) { count.value++ }
}
このスクラップは2023/08/13にクローズされました