Home > OS >  Filtering collection inside onEach in Flow doesn't work
Filtering collection inside onEach in Flow doesn't work

Time:12-08

In particular use case I'm making a repository call to obtain data in form of Flow.

It's type is:

Flow<Resource<List<Location>>>

Where:

  • Resource is wrapper class:

     sealed class Resource<T>(val data: T? = null, val message: String? = null) {
        class Loading<T>(data: T? = null): Resource<T>(data)
        class Success<T>(data: T?): Resource<T>(data)
        class Error<T>(message: String, data: T? = null): Resource<T>(data, message)}
    
  • Location is my data model class

Each location has it's own property like type. When user switch to section where type's Hotel, use case method is triggered, api call is made and I'm filtering list so that it contains only desirable items.

However the problem is filtering mechanims which doesn't work.

return repository.getLocations()
        .onEach { result ->
            if (result.data != null) {
                when (locationType) {
                    is LocationType.All -> result.data
                    is LocationType.Hotel -> result.data.filter { it.type == "Hotel" }
                    is LocationType.Explore -> result.data.filter { it.type == "Explore" }
                    is LocationType.Active -> result.data.filter { it.type == "Active" }
                    is LocationType.Restaurant -> result.data.filter { it.type == "Restaurant" }
                }   
            }
        }

Final list isn't changed despite filtering with use ofonEach

UPDATE

The return type of repository call is:

Flow<Resource<List<Location>>>

CodePudding user response:

If you want to filter the result you should filter it directly . the onEach is not needed here. You can do it this way .

val result = repository.getLocations()
    return if(result.data!=null){
        result.data.filter { item ->
            when (locationType) {
                is LocationType.Hotel -> item.type == "Hotel"
                is LocationType.Explore -> item.type == "Explore"
                is LocationType.Active -> item.type == "Active"
                is LocationType.Restaurant ->item.type == "Restaurant"
                else -> true
            }
        }
    }else{
        emptyList()
    }

this is just for explanation you can modify and make it more kotlinify. LocationType is a constant here so you can directly do something like this you do not need a when here.

val result = repository.getLocations()
    return if(result.data!=null){
        result.data.filter { item ->
                item.type == locationType
        }
    }else{
        emptyList()
    }

CodePudding user response:

filter doesn't filter a list in place. It returns a filtered copy of the list.

It also doesn't make sense to use onEach. You are repeatedly redundantly filtering your same list as many times as it has items. And you are throwing away all those filtered copies since you do nothing with them. onEach is for performing a side-effect action on each item and returning the original list.

You need to create your filtered copy one time. Then you can put that filtered copy back into a new Success instance to return.

val result repository.getLocations()
if (result !is Success<List<Location>>) {
    return result
}
val filteredData = result.data?.filter {
    it.type == when (locationType) {
        is LocationType.All -> it.type
        is LocationType.Hotel -> "Hotel"
        is LocationType.Explore -> "Explore"
        is LocationType.Active -> "Active"
        is LocationType.Restaurant -> "Restaurant"
    }
}
return Success(data = filteredData)

Side note, you are missing the point of using a sealed class. Since you are putting all the possible properties in the parent class, there's no point in making it sealed and giving it children--it could just have one more property that indicates it represents loading, success, or error. Now you have to deal with nullable data and error message even if you've checked the child type. The whole point of using a sealed class vs. a single class would be to avoid having to make those nullable. Your parent class should have no properties defined. The Loading class doesn't need properties and can therefore be an object. Your Success class can have a single non-nullable data property, and the Error class can have a single non-nullable message property. The Success and Error classes can be data classes so they are more easily compared. It should look like this:

sealed class Resource<T> {
    object Loading<T>: Resource<T>()
    data class Success<T>(val data: T): Resource<T>()
    data class Error<T>(message: String): Resource<T>()
}
  • Related