0

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>>>

SOLUTION

Finally I've come up with the solution. In my use case I'm collecting the flow and inside collect lambda, I've put code responsible for filtering. When everything is done I'm just emitting data further ;)

operator fun invoke(
    locationType: LocationType
) = flow {
     repository.getLocations().collect { 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" }.also { res ->
                when (result) {
                    is Resource.Success -> {
                        emit(Resource.Success(data = res))   }
                    is Resource.Loading -> {
                        emit(Resource.Loading(data = res))
                    }
                    is Resource.Error -> {
                        emit(Resource.Error(data = res, message = result.message ?: ""))
                    }
                }
            }
General Grievance
  • 4,555
  • 31
  • 31
  • 45
amtrax
  • 466
  • 7
  • 20

2 Answers2

1

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()
    }

To return a Flow you can return something like below.

result.map { it.data.filter { item -> item.type == locationType } }
ADM
  • 20,406
  • 11
  • 52
  • 83
1

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 on your Flow. This is only creating a new Flow that will perform the onEach action on each emitted item when it gets collected. Since you are returning from this function, maybe you just need the first item from the Flow, in which case you should use the first() function on it and then work with the returned value.

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().first()
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>()
}
Tenfour04
  • 83,111
  • 11
  • 94
  • 154
  • Thank you for your answer. Now I understand what onEach does ;) So if I wanted to filter some data from Flow like that, there's no other way than collect these items first and later do some filtering? Since repository call returns Flow. – amtrax Dec 07 '21 at 20:59
  • Oh, I missed that. I was thinking of `List.onEach`. `Flow.onEach` does not immediately iterate the Flow like `List.onEach` does. It returns a new Flow that wraps the original Flow and will perform the lambda action when the new Flow is collected. If you need to do something only with the first value of a Flow, you can call `first()` on it to get the first emitted item. – Tenfour04 Dec 07 '21 at 21:18