androidkotlinandroid-jetpack-composeviewmodelkotlin-stateflow

Why is initialValue needed in collectAsStateWithLifecycle for StateFlow?


I have a ViewModel that exposes a StateFlow representing the UI state of a book list:

class BooksViewModel(private val getBooksUseCase: GetBooksUseCase) : ViewModel() {

    val booksState: Flow<BooksUiState> = flow {
        val result = getBooksUseCase()
        result.onSuccess {
            emit(BooksUiState.Success(it))
        }.onFailure {
            emit(BooksUiState.Error(it))
        }
    }.onStart {
        emit(BooksUiState.Loading(true))
    }.onCompletion {
        emit(BooksUiState.Loading(false))
    }.stateIn(
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(5000),
        initialValue = BooksUiState.Loading(true),
    )
}

In my @Composable, I collect this state using collectAsStateWithLifecycle:

@Composable
fun BookScreen(
    onBackPressed: () -> Unit,
    viewModel: BooksViewModel = koinViewModel()
) {
    val uiState by viewModel.booksState.collectAsStateWithLifecycle(
        initialValue = BooksUiState.Loading(true)
    )

    BackHandler(onBack = onBackPressed)

    BookContent()
}

Since booksState is already a StateFlow with an initial value (BooksUiState.Loading(true)), why do we need to pass initialValue to collectAsStateWithLifecycle again?

Is there a way to avoid this redundancy while ensuring correct state collection in Compose?


Solution

  • You declared booksState to be a Flow<BooksUiState>, although the actual object is a StateFlow<BooksUiState>. collectAsStateWithLifecycle() only sees a regular Flow and that usually has no initial value, so it forces you to provide one.

    Just properly declare booksState as

    val booksState: StateFlow<BooksUiState>
    

    Then collectAsStateWithLifecycle() doesn't need an initial value any more.