Search code examples
androidkotlinkotlin-flow

Flow<List> from Room to UiState using StateFlow


class HomeViewModel(
    savedStateHandle: SavedStateHandle,
    private val categoryRepository: CategoryRepository,
    private val expenseRepository: ExpenseRepository
): ViewModel() {
    private val month: Int = savedStateHandle[HomeDestination.monthArgs] ?: Calendar.MONTH
    private val year: Int = savedStateHandle[HomeDestination.yearArgs] ?: Calendar.YEAR

    private val _categories: Flow<List<Category>> = categoryRepository.getAllCategoriesStream()
    private val _expenses: Flow<List<Expense>> = expenseRepository.getAllExpensesBetweenStream(
        LocalDate.of(year, month, 1).toEpochDay(),
        LocalDate.of(year, if (month + 1 == 13) 1 else month + 1, 1).toEpochDay()
    )

    private var _uiState = MutableStateFlow(UiState())
    val uiState: StateFlow<UiState> = _uiState.asStateFlow()

    data class UiState(
        val categories: List<CategoryUiState> = listOf(),
        val expenses: List<ExpenseUiState> = listOf()
    )
}

I want to link the value of _expenses to expenses in the UiState and do it so it updates the expenses if _expenses content updates.


Solution

  • Assuming the issue is not how you can store an Expense object as an ExpenseUiState, I'll expect there to be some helper function that creates a proper UiState from a Category and an Expense. Let's call that UiState.of(...).

    Then you can replace _uiState and uiState with this:

    val uiState: StateFlow<UiState> = combine(_categories, _expenses) { categories, expenses ->
        UiState.of(categories, expenses)
    }.stateIn(
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(5_000),
        initialValue = UiState.Default,
    )
    

    This combines multiple flows into a single flow using UiState.of to create the resulting content. Then it is converted to a StateFlow. That needs an initial value, so I invented a UiState.Default. Replace that with whatever you want the flow to return until the first values are produced from _categories and _expenses. Could be null if you wanted.