はじめに
こんにちは、どすこいです。
Androidエンジニアのみなさん、Compose楽しんでますか?
僕は楽しんでます。
今回jetpackのlifecycle-runtime-compose:2.6.0-alpha
でcollectAsStateWithLifecycle()
なるものが追加されたので早速触っていきたいと思います。
前提
まずComposeで扱う値をUiStateにまとめたものとします。
そのUiStateをViewModel側でStateFlowとして持っているという状態です。
こんな感じ
class MainViewModel : ViewModel() {
val _state: MutableStateFlow<UiState> = MutableStateFlow(UiState())
val state: StateFlow<UiState> = _state.asStateFlow()
data class UiState(
val hoge: String = ""
)
}
このStateFlowをComposable側で監視する際にどうやっていくのかを見ていきます
試してみる
Lifecycleを考慮しない場合のStateFlowの監視方法
これだとLifecycleが何も考慮されてないのでアプリがバックグラウンドにある状態でStateFlowが更新されるとrecompositionされてしまうと思います。
@Composable
fun MainScreen(mainViewModel: MainViewModel) {
val state by mainViewModel.state.collectAsState(initial = MainViewModel.UiState())
}
Lifecycleを考慮した場合のStateFlowの監視方法
こちらだとflowWithLifecycle
を使っており、中でrepeatOnLifecycle
を使っているので、指定したLifecycle.State
外でStateFlowが更新されてもrecompositionされません。
つまりLifecycle.State.STARTED
からLifecycle.State.STOPED
より外でStateFlowが変更されてもrecompositionされないというわけですね。
ただ毎回これを書くのはめんどくさいので、多くのエンジニアは独自で共通の関数を作ってたと思います。
@Composable
fun MainScreen(mainViewModel: MainViewModel) {
val lifecycleOwner = LocalLifecycleOwner.current
val flowLifecycleAware = remember(key1 = mainViewModel, key2 = lifecycleOwner) {
mainViewModel.state.flowWithLifecycle(lifecycleOwner.lifecycle, Lifecycle.State.STARTED)
}
val state by flowLifecycleAware.collectAsState(initial = MainViewModel.UiState())
}
collectAsStateWithLifecycle()
を使った場合の監視方法
こちらのIssueTrackerにもあるように、デフォで上記のことをやってくれる関数を用意してくれとの要望があったそうです。
Googleさんはとても優しいのでLifecycleを考慮したcollectAsState
を用意してくれました
@ExperimentalLifecycleComposeApi
@Composable
fun <T> Flow<T>.collectAsStateWithLifecycle(
initialValue: T,
lifecycle: Lifecycle,
minActiveState: Lifecycle.State = Lifecycle.State.STARTED,
context: CoroutineContext = EmptyCoroutineContext
): State<T> {
return produceState(initialValue, this, lifecycle, minActiveState, context) {
lifecycle.repeatOnLifecycle(minActiveState) {
if (context == EmptyCoroutineContext) {
this@collectAsStateWithLifecycle.collect { this@produceState.value = it }
} else withContext(context) {
this@collectAsStateWithLifecycle.collect { this@produceState.value = it }
}
}
}
}
デフォルトのminActiveState
がLifecycle.State.STARTED
なので特にこちら側で何か指定しない限りはLifecycle.State.STARTED
からLifecycle.State.STOPED
まではrecompositionしてくれますが、それより外のLifecycleでStateFlowが更新されてもrecompositionされないということですね。
下が実装例です。
@Composable
fun MainScreen(mainViewModel: MainViewModel) {
// デフォがLifecycle.State.STARTEDなので基本的には何も指定しなくて良い
val state by mainViewModel.state.collectAsStateWithLifecycle()
}
注意
artifactがlifecycle-runtime
ではなくて、新しく追加されたlifecycle-runtime-compose
なので注意
あとはまだalpha版なので実戦投入するときは注意してください。