0

I'm currently working on an Android application utilizing the MVI (Model-View-Intent) architecture. In my ViewModel, I'm using a MutableStateFlow to manage the UI state. I want to optimize my code to prevent repetitive work when checking response requests in my ViewModel's state.

I've implemented the following approach to handle response requests in my ViewModel:

@HiltViewModel
class ListViewModel @Inject constructor(private val repository: ListRepository) : ViewModel() {
    val intentChannel = Channel<ListIntent>()
    private val _state = MutableStateFlow<ListState>(ListState.Idle)
    val state: StateFlow<ListState> get() = _state

    init {
        handleIntents()
    }

    private fun handleIntents() = viewModelScope.launch {
        intentChannel.consumeAsFlow().collect { intent ->
            when (intent) {
                is ListIntent.LoadFiltersLetters -> fetchingFiltersList()
                is ListIntent.LoadRandom -> fetchingRandomFood()
                is ListIntent.LoadCategoriesList -> fetchingCategoriesList()
                is ListIntent.LoadFoods -> fetchingFoodsList(intent.letter)
                is ListIntent.LoadSearchFoods -> fetchingSearchFood(intent.search)
                is ListIntent.LoadFoodByCategory -> fetchingFoodsByCategory(intent.category)
            }
        }
    }

    private fun fetchingFoodsByCategory(category: String) = viewModelScope.launch {
        val response = repository.foodByCategory(category)
        _state.emit(ListState.LoadingFoods)
        when (response.code()) {
            in 200..202 -> {
                _state.value = if (response.body()!!.meals != null) {
                    ListState.FoodsList(response.body()!!.meals!!)
                } else {
                    ListState.Empty
                }
            }
            in 400..499 -> {
                _state.emit(ListState.Error(""))
            }
            in 500..599 -> {
                _state.emit(ListState.Error(""))
            }
        }
    }

    private fun fetchingSearchFood(search: String) = viewModelScope.launch {
        val response = repository.searchFood(search)
        _state.emit(ListState.LoadingFoods)
        when (response.code()) {
            in 200..202 -> {
                _state.value = if (response.body()!!.meals != null) {
                    ListState.FoodsList(response.body()!!.meals!!)
                } else {
                    ListState.Empty
                }
            }
            in 400..499 -> {
                _state.emit(ListState.Error(""))
            }
            in 500..599 -> {
                _state.emit(ListState.Error(""))
            }
        }
    }

    private fun fetchingFoodsList(letter: String) = viewModelScope.launch {
        val response = repository.foodsList(letter)
        _state.emit(ListState.LoadingFoods)
        when (response.code()) {
            in 200..202 -> {
                _state.value = if (response.body()!!.meals != null) {
                    ListState.FoodsList(response.body()!!.meals!!)
                } else {
                    ListState.Empty
                }
            }
            in 400..499 -> {
                _state.emit(ListState.Error(""))
            }
            in 500..599 -> {
                _state.emit(ListState.Error(""))
            }
        }
    }

    private fun fetchingCategoriesList() = viewModelScope.launch {
        val response = repository.categoriesList()
        _state.emit(ListState.LoadingCategory)
        when (response.code()) {
            in 200..202 -> {
                _state.emit(ListState.CategoriesList(response.body()!!.categories))
            }
            in 400..499 -> {
                _state.emit(ListState.Error(""))
            }
            in 500..599 -> {
                _state.emit(ListState.Error(""))
            }
        }
    }

    private suspend fun fetchingRandomFood() {
        val response = repository.randomFood()
        when (response.code()) {
            in 200..202 -> {
                _state.emit(ListState.RandomFood(response.body()?.meals?.get(0)))
            }
            in 400..499 -> {
                _state.emit(ListState.Error(""))
            }
            in 500..599 -> {
                _state.emit(ListState.Error(""))
            }
        }
    }

    private suspend fun fetchingFiltersList() {
        val list = listOf('A'..'Z').flatten().toMutableList()
        _state.emit(ListState.FilterLetters(list))
    }
}
 when (response.code()) {
            in 200..202 -> {
            }
            in 400..499 -> {
            }
            in 500..599 -> {
            }
}

However, I find myself repeating similar code for different functions, which is not optimal I've heard that using coroutines and flows can help in avoiding this repetition. Could you please guide me on how to improve this code using coroutines and flows to minimize redundancy while efficiently handling response requests? Your insights and code examples would be greatly appreciated. Thank you!

I use mvi architecture

 when (response.code()) {
            in 200..202 -> {
            }
            in 400..499 -> {
            }
            in 500..599 -> {
            }
}

I want to do this in the ViewModel, not in the repository, and avoid duplicate code

Noah
  • 1
  • 1

2 Answers2

0

Transfer all this code to the repository and just change the state flow value in the viewModel and observe that in your Fragment/Activity. Make an enum class having int values for representing these methods. Just send the enum type and query string to repository. have a switch there based on these enum types and call database methods for foods and return a list accordingly. Hope u understood.

Hassaan
  • 1
  • 1
  • 1
    If you can give an example, you gave the explanation in general, I did not understand – Noah Sep 01 '23 at 06:05
0

Look at what's common between all these functions:

  • They fetch a Retrofit response.
  • They emit a Loading state to the StateFlow (although, don't you want to show that before you do the fetch???)
  • They parse the same groups of response codes. Errors are treated the same for all of them, and codes in the 200 range always emit something.
  • They all could be converted to suspend functions. Currently you have some of them launching new coroutines when they could just be suspend functions themselves. And it wouldn't hurt to turn all of them into suspend functions.

So you can create a function that combines all the common stuff and takes lambda arguments for the parts that are different.

private suspend fun <T> doFetch(
    makeRequest: suspend ()->Response<T>,
    loadingType: ListState,
    parseBodyToState: (T)->ListState
) {
    _state.value = loadingType
    val response = makeRequest()
    _state.value = when (response.code()) {
        in 200..202 -> parseBodyToState(response.body()!!)
        in 400..499 -> ListState.Error("4xx error")
        in 500..599 -> ListState.Error("5xx error")
        else -> ListState.Error("Unknown error")
    }    
}

Also, you are doing the same thing whenever the response type has a list of meals, so that can be a function, too:

private fun parseMealsToState(body: YourResponseBodyType): ListState {
    val meals = body.meals
    return if (meals != null) ListState.FoodsList(meals) else ListState.Empty
}

Then your repetitive functions can be changed as follows:

private suspend fun fetchingFoodsByCategory(category: String) = doFetch(
    { repository.foodByCategory(category) },
    ListState.LoadingFoods,
    ::parseMealsToState
)

private suspend fun fetchingSearchFood(search: String) = doFetch(
    { repository.searchFood(search) }.
    ListState.LoadingFoods,
    ::parseMealsToState
)

private suspend fun fetchingFoodsList(letter: String) = doFetch(
    { repository.foodsList(letter) },
    ListState.LoadingFoods,
    ::parseMealsToState
)

private suspend fun fetchingCategoriesList() = doFetch(
    { repository.categoriesList() },
    ListState.LoadingCategory
) { ListState.CategoriesList(it.categories) }

private suspend fun fetchingRandomFood() = doFetch(
    { repository.randomFood() },
    ListState.Idle,
) { ListState.RandomFood(it.meals?.get(0)) }
Tenfour04
  • 83,111
  • 11
  • 94
  • 154