kotlin combine flow has a slight delay from the dependent flows, thus causing issue in android compose view

31 views Asked by At

I have a ListScreen which displays list items loaded from internet with the option to filter the items.

So, I have two states here: future and filter (see ViewModel code). My list state is combined flow of these two states, with initial value of empty list.

In my Compose screen, I want to show different view when list state is empty, otherwise the list items. The issue is that, when future state changes from LOADING to SUCCESS, the list state doesn't change from empty list to future result immediately, but a slight delay is observed. Due to this slight delay, sometimes the empty list screen is shown for a slight moment (See comment in Compose code).

ViewModel code:

class ListViewModel<T>(
    val path: String,
    val repository: MyRepository<T>,
): ViewModel() {
    enum class State { LOADING, SUCCESS, FAILURE }

    private val _futureState = MutableStateFlow(State.LOADING)
    val futureState = _futureState.asStateFlow()
    
    private lateinit var _futureError: Throwable
    val futureError get() = _futureError
    
    private lateinit var _futureResult: List<T>
    
    init {
        viewModelScope.launch(Dispatchers.IO) { 
            try {
                _futureResult = repository.get(path) // suspend fun returns List<T>
                _futureState.update { State.SUCCESS }
            } catch (error: Throwable) {
                _futureError = error
                _futureState.update { State.FAILURE }
            }
        }
    }
    
    private val _filterState = MutableStateFlow<String?>(null)
    val filterState = _filterState.asStateFlow()
    
    fun setFilter(value: String?) {
        _filterState.update { value }
    }
    
    val listState = combine(futureState, filterState) { state, filter ->
        if (state == State.SUCCESS) {
            if (!filter.isNullOrBlank()) {
                _futureResult.filter { 
                    // filter predicate
                }  
            } else _futureResult
        } else emptyList()
    }.stateIn(
        viewModelScope,
        SharingStarted.WhileSubscribed(1000),
        emptyList(),
    )
}

Compose code:

@Composable
fun <T> ListScreen(
    path: String,
    repository: MyRepository<T>,
) {
    val vm: ListViewModel<T> = viewModel(
        factory = viewModelFactory {
            initializer {
                ListViewModel(path, repository)
            }
        }
    )

    val state by vm.futureState.collectAsState()
    val filter by vm.filterState.collectAsState()
    val list by vm.listState.collectAsState()

    Scaffold {
        when (state) {
            State.LOADING -> { /* loading screen */ }
            State.FAILURE -> { /* error message screen */ }
            State.SUCCESS -> {
                if (list.isEmpty()) {
                    /* no results found screen */

                    // issue: when state changes from LOADING -> SUCCESS,
                    // this screen is shown for a very short duration

                    // note: the issue is only observed sometimes 
                } else {
                    /* list items screen */
                }
            }
        }
    }
}
0

There are 0 answers