在 Flow 中的 onEach 中过滤集合不起作用

Posted

技术标签:

【中文标题】在 Flow 中的 onEach 中过滤集合不起作用【英文标题】:Filtering collection inside onEach in Flow doesn't work 【发布时间】:2022-01-12 11:32:49 【问题描述】:

在特定用例中,我正在调用存储库以获取 Flow 形式的数据。

它的类型是:

Flow<Resource<List<Location>>>

地点:

资源是包装类:

 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)

位置是我的数据模型类

每个位置都有自己的属性,例如类型。当用户切换到类型酒店的部分时,会触发用例方法,进行 api 调用,我正在过滤列表,使其仅包含所需的项目。

但问题是过滤机制不起作用。

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" 
                   
            
        

尽管使用onEach 进行过滤,但最终列表并未更改

更新

存储库调用的返回类型为:

Flow<Resource<List<Location>>>

解决方案

我终于想出了解决方案。 在我的用例中,我正在收集流并在collect lambda 内部,我已经放置了负责过滤的代码。 当一切都完成后,我只是进一步发送数据;)

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 ?: ""))
                    
                
            

【问题讨论】:

【参考方案1】:

如果你想过滤结果,你应该直接过滤它。这里不需要onEach。 你可以这样做。

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

这只是为了解释您可以修改并使其更多kotlinifyLocationType 在这里是一个常数,所以你可以直接做这样的事情,你不需要在这里when

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

要返回 Flow,您可以返回如下所示的内容。

result.map  it.data.filter  item -> item.type == locationType  

【讨论】:

感谢您的承诺,请看帖子更新。 您过滤的方法的返回类型是什么? Flow&lt;Resource&lt;List&lt;Location&gt;&gt;&gt;【参考方案2】:

filter 不会就地过滤列表。它返回列表的过滤副本

在您的 Flow 上使用 onEach 也没有任何意义。这只是创建一个新流,它将在收集到每个发出的项目时对其执行onEach 操作。由于您是从这个函数返回的,也许您只需要 Flow 中的第一项,在这种情况下,您应该对其使用 first() 函数,然后使用返回的值。

您需要创建一次过滤副本。然后,您可以将过滤后的副本放回新的 Success 实例以返回。

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)

旁注,您错过了使用密封类的要点。由于您将所有可能的属性都放在父类中,因此将其密封并赋予其子类是没有意义的——它可能只有一个属性来指示它表示加载、成功或错误。现在,即使您检查了子类型,您也必须处理可空的 data 和错误 message。使用密封类与单个类的全部意义在于避免使它们可以为空。您的父类不应定义任何属性。 Loading 类不需要属性,因此可以是object。您的 Success 类可以有一个不可为空的 data 属性,而 Error 类可以有一个不可为空的 message 属性。 Success 和 Error 类可以是 data 类,因此它们更容易比较。它应该是这样的:

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

【讨论】:

感谢您的回答。现在我明白了 onEach 的作用 ;) 所以如果我想像这样从 Flow 中过滤一些数据,除了先收集这些项目然后再进行一些过滤之外,别无他法?由于存储库调用返回 Flow。 哦,我错过了。我在想List.onEachFlow.onEach 不会像 List.onEach 那样立即迭代 Flow。它返回一个包装原始 Flow 的新 Flow,并在收集新 Flow 时执行 lambda 操作。如果您只需要对 Flow 的第一个值做某事,您可以在其上调用 first() 以获取第一个发出的项目。

以上是关于在 Flow 中的 onEach 中过滤集合不起作用的主要内容,如果未能解决你的问题,请参考以下文章