Search code examples
kotlinkotlin-coroutinesclean-architecture

How to include an error case in the UIState?


I'm trying to catch any error cases in the ViewModel and update the UIState, but it's not working.

This is the sealed interface to handle the different responses:

sealed interface UniversalisUiState {
    data object Loading : UniversalisUiState

    data class UniversalisData(
        val selectedTopicId: String,
        val topics: UniversalisResource,
    ) : UniversalisUiState

    data object Empty : UniversalisUiState

    data class UniversalisError(
        val message: String,
    ) : UniversalisUiState

}

In the ViewModel the relevant code is:

val uiState: StateFlow<UniversalisUiState> = combine(
    selectedTopicId,
    getUniversalisUseCase.invoke(
        date = selectedDate.value,
        title = route.initialTopicId!!,
        selectedTopicId = LiturgyHelper.liturgyByName(route.initialTopicId!!)
            .toString()
    ).catch {
        //NOT WORKING *******
        //emit(UniversalisUiState.UniversalisError(it.message))
        Timber.d("aaa", it.message)
    },
    UniversalisUiState::UniversalisData,
).stateIn(
    scope = viewModelScope,
    started = SharingStarted.WhileSubscribed(5_000),
    initialValue = Loading,
)

And in the UseCase:

class GetUniversalisUseCase @Inject constructor(
    private val universalisRepository: UniversalisRepository,
    private val userDataRepository: UserDataRepository,
) {

    operator fun invoke(
        date: Int,
        selectedTopicId: String,
        title: String
    ): Flow<UniversalisResource>
    {
        return combine(
            userDataRepository.userData,
            universalisRepository.getUniversalisByDate(
                UniversalisResourceQuery(
                    filterDate = date,
                    selectedTopic = selectedTopicId.toInt()
                )
            ),
        ) { userData, universalis ->
            if (universalis.todayDate == 0 && selectedTopicId != "30") {
                universalisRepository.insertFromRemote(
                    UniversalisResourceQuery(
                        date,
                        selectedTopicId.toInt()
                    )
                )
            }
            UniversalisResource(
                date = universalis.todayDate, 
                title = title,
                name = "",
                id = selectedTopicId.toInt(),
                data = universalis,
                dynamic = userData
            )
        }
    }
}

How I can do this: emit(UniversalisUiState.UniversalisError(it.message)) when catching errors? I can see de error of Timber, but I can't emit UniversalisError.


Solution

  • Since you want to emit a UniversalisUiState, you need to move the catch after the combine, when the flow is already of that type:

    val uiState: StateFlow<UniversalisUiState> = combine(
        selectedTopicId,
        getUniversalisUseCase.invoke(
            date = selectedDate.value,
            title = route.initialTopicId!!,
            selectedTopicId = LiturgyHelper.liturgyByName(route.initialTopicId!!)
                .toString()
        ),
        UniversalisUiState::UniversalisData,
    ).catch<UniversalisUiState> {
        emit(UniversalisUiState.UniversalisError(it.message))
        Timber.d("aaa", it.message)
    }.stateIn(
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(5_000),
        initialValue = UniversalisUiState.Loading,
    )
    

    You might need to explicitly specify the interface UniversalisUiState as the generic type of catch, so Kotlin can correctly infer the type of the resulting flow.