概要
AndroidでUI状態を管理するときにいろいろやり方があると思いますが、UiState状態をdata classで定義しておいて使用する方法があると思います。
使用する際に「これどうすれば良いんだっけ?」と思うことがあるのである程度のお決まりみたいなものをまとめておきます。
定義するとき
SampleViewModel.kt
// ViewModelの外に定義しておく
// プロパティ多いようだったら別ファイルにした方が良いかも
data class SampleUiState(
val message: String = "",
val loading: Boolean = false,
val user: User? = null
)
@HiltViewModel
class SampleViewModel @Inject constructor() {
....
}
ViewModel内で使うとき
updateとcopyを使っていきます。ただコードが長くなりがちなので、拡張関数を何かしら作っておくと良いかもしれません。
なぜupdateを使うのかというと、スレッドセーフかつアトミック(完全に行われるか全く行われないかのいずれか)な方法だからです。
SampleViewModel.kt
@HiltViewModel
class SampleViewModel @Inject constructor() {
private val _uiState = MutableStateFlow(SampleUiState())
val uiState: StateFlow = _uiState.asStateFlow()
fun getUser() {
_uiState.update { state ->
state.copy(
loading = true
)
}
viewModelScope.launch {
try {
val user = userRepository.getUser()
_uiState.update { state ->
state.copy(
user = user
)
}
} catch (e: Exception) {
_uiState.update { state ->
state.copy(
message = "error"
)
}
} finally {
_uiState.update { state ->
state.copy(
loading = false
)
}
}
}
}
}
Compose Viewで使うとき
単純にViewModelで個別に値を持っているよりuiStateで持っている方がviewModel.hoge.valueみたいに書かなくて済むのでちょっと楽です。
SampleComposable.kt
@Composable
fun SampleComposable(
viewModel: SampleViewModel
) {
// ライフサイクルを考慮する場合はcollectAsStateWithLifecycle()を使った方が良い
val uiState by viewModel.uiState.collectAsState()
Text(text = uiState.message)
}
Activityで使うとき
SampleActivity.kt
@AndroidEntryPoint
class SampleActivity : AppCompatActivity() {
val viewModel: SampleViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
..
lifecycleScope.launch {
// repeatOnLifecycleはライフサイクルに応じてJobを操作してくれる
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { uiState ->
..
}
}
}
}
}
まとめ
場合によってベストプラクティスは変わってくるかと思いますが、大体のやり方は似てくるかと思います。
もっと良いやり方があったら適宜追記します。
参考