@Composable
fun <T> StateFlow<T>.collectAsState(
context: CoroutineContext = EmptyCoroutineContext
): State<T> = collectAsState(value, context)
@Composable
fun <T : R, R> Flow<T>.collectAsState(
initial: R,
context: CoroutineContext = EmptyCoroutineContext
): State<R> = produceState(initial, this, context) {
if (context == EmptyCoroutineContext) {
collect { value = it }
} else withContext(context) {
collect { value = it }
}
}
例えばサーバーからデータをとってきて表示する画面があり、画面の状態を表す UiState が次のようになっているとします。
sealed interface UiState {
object Initial : UiState
object Loading : UiState
data class Error(val e: Exception) : UiState
data class Success(val profile: Profile) : UiState
}
(* 私は基本的には ViewModel からは StateFlow ではなく State を公開するようにしています。)
Flow.collectAsState() の場合
class ProfileViewModel : ViewModel() {
val uiState: Flow<UiState> = ...
...
}
@Composable
fun ProfileScreen(
viewModel: ProfileViewModel
) {
ProfileContent(
uiState = viewModel.uiState.collectAsState(initial = UiState.Initial).value
)
}
@Composable
private fun ProfileContent(
uiState: UiState
) {
when (uiState) {
UiState.Initial,
UiState.Loading -> {
...
}
is UiState.Error -> {
...
}
is UiState.Success -> {
...
}
}
}
Flow.collectAsState() の場合、ProfileContent の (re)compose およびそのときの UiState は次のようになります。
UiState.Initial → (ViewModel でデータ取得開始)→ UiState.Loading → (取得成功)→ UiState.Success
ここで ProfileScreen から Navigation Compose で別の画面に行って戻ってくると、一瞬 UiState.Initial になります。
UiState.Success → (Navigation Compose で別の画面に行って戻ってくる) → UiState.Initial → UiState.Success
StateFlow.collectAsState() の場合
class ProfileViewModel : ViewModel() {
val uiState: StateFlow<UiState> = ...
...
}
@Composable
fun ProfileScreen(
viewModel: ProfileViewModel
) {
ProfileContent(
uiState = viewModel.uiState.collectAsState().value
)
}
StateFlow.collectAsState() の場合、データ取得完了までの UiState の流れは同じです。
UiState.Initial → (ViewModel でデータ取得開始)→ UiState.Loading → (取得成功)→ UiState.Success
一方、ここで ProfileScreen から Navigation Compose で別の画面に行って戻ってきても UiState.Initial にはなりません。
UiState.Success → (Navigation Compose で別の画面に行って戻ってくる) → UiState.Success
そのため、ViewModel から StateFlow を公開して StateFlow の collectAsState() を使ったほうがよいです。
stateIn() を使えば Flow を StateFlow に変換することができます。