Search code examples
kotlinandroid-roomkotlin-coroutinesandroid-mvvmkotlin-stateflow

filter flowable list of object by category and group into another list of object in flow


is it possible to filter and group Flow<List<Object A>> by categories.

i found issue quite similar here , but no luck :(

here i'm sharing my approach which i tried,

code inside viewModel :

class HomeViewModel: ViewModel() {

 data class Car(val id: Int, val name: String, val category: Int)
 data class CarsByCategory(val categoryId:Int, val categoryName: String, val carList: List<Car>)

    private val categoryList =
        mapOf<Int, String>(1 to "Audi", 2 to "BMW", 3 to "Chevrolet", 4 to "Dodge", 5 to "Others")

    private val mutableCarList: MutableList<CarsByCategory> = mutableListOf()
    private val _mutableStateFlowCarList: MutableStateFlow<List<CarsByCategory>> = MutableStateFlow(emptyList())
    
    val filteredCarList: StateFlow<List<CarsByCategory>> = _mutableStateFlowCarList
    
    private fun getAllCarsAsFlow(): Flow<List<Car>> {
        val cars = listOf(
            Car(id = 1, name = "A1", category = 1),
            Car(id = 1, name = "A2", category = 1),
            Car(id = 1, name = "BMW X1", category = 2),
            Car(id = 1, name = "BMW X7", category = 2),
            Car(id = 1, name = "M Roadster", category = 2),
            Car(id = 1, name = "Bolt EUV", category = 3),
            Car(id = 1, name = "Blazer", category = 3),
            Car(id = 1, name = "Challenger", category = 4),
            Car(id = 1, name = "Neon", category = 4),
            Car(id = 1, name = "Frontier", category = 5)
        )
        return flowOf(cars)
    }
  
   private fun filterCarByCategory(){
        getAllCarsAsFlow().map { carList ->
            for (key in categoryList.keys) {
                val filteredList = carList.filter { car -> car.category == key }
                mutableCarList.add(
                    CarsByCategory(
                        categoryId = key,
                         categoryName= categories.getValue(key),
                        carList = filteredList
                    )
                )
            }
            _mutableStateFlowCarList.value = mutableCarList.toList()
        }
    }

    init {
        filterCarByCategory()
    }
}

code in fragment:

  ....

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

        lifecycleScope.launchWhenStarted {
            homeViewModel.filteredCarList.collect {
                Log.d(TAG, "onViewCreated: ${it.size}")
//                here getting car list size is 0
            }
        }
    }

  ...

i don't know is it right approach or not , please let me know how to solve this problem using flow


Solution

  • A flow doesn't emit any value until it is collected. So you need to collect your flow:

    private fun filterCarByCategory(){
        viewModelScope.launch {
            getAllCarsAsFlow().collect { carList ->
                // Rest everything same
            }
        }
    }
    

    Edit: If the sole purpose of _mutableStateFlowCarList is to provide data to UI, you need not use a StateFlow here, just a normal Flow would do the job.

    val filteredCarList = getAllCarsAsFlow().map { carList ->
        categoryList.map { (id, name) ->
            CarsByCategory (
                categoryId = id,
                categoryName = name,
                carList = carList.filter { car -> car.category == id }
            )
        }
    }