【问题标题】:Kotlin - How to properly map result using Either?Kotlin - 如何使用 Either 正确映射结果?
【发布时间】:2019-02-28 22:18:58
【问题描述】:

我正在尝试从数据库中读取对象列表并将其映射到另一种类型的列表。

// Returns either a Failure or the expected result
suspend fun getCountries(): Either<Failure, List<CountryItem>> {
    // Get the result from the database
    val result = countryLocalDataSource.getCountries()

    // Left means Failure
    if (result.isLeft) {
        // Retrieve the error from the database
        lateinit var error: Failure
        result.either({
            error = it
        }, {})

        // Return the result
        return Either.Left(error)
    }

    // The database returns a List of Country objects, we need to map it to another object (CountryItem)
    val countryItems: MutableList<CountryItem> = mutableListOf()

    // Iterate the Country List and construct a new List of CountryItems
    result.map { countries -> {
        countries.forEach {
            // Assign some values from an Enum (localized string resources)
            val countryEnumValue = Countries.fromId(it.id)
            countryEnumValue?.let { countryIt ->
                val countryStringNameRes = countryIt.nameStringRes;

                // Create the new CountryItem object (@StringRes value: Int, isSelected: Bool)
                countryItems.add(CountryItem(countryStringNameRes, false))
            }
        }
    } }

    // Because this is a success, return as Right with the newly created List of CountryItems
    return Either.Right(countryItems)
}

为了便于阅读,我没有包含整个 RepositoryDAO 类,我在上面的代码 sn-p 中留下了 cmets。

简而言之:我正在使用 Kotlin 的 Coroutines 在单独的线程中访问数据库,并且我正在处理 UI Thread 上的响应。使用Either 类返回两个不同的结果(失败或成功)。

上面的代码可以用,但是太丑了。这是交付结果的正确方法吗?

我要做的是重构上面的代码。

整个问题是由两种不同的对象类型引起的。 Database Data Source API 正在返回一个Either&lt;Failure, List&lt;Country&gt;&gt;,同时该函数应该返回一个Either&lt;Failure, List&lt;CountryItem&gt;&gt;

我无法直接从Database Data Source API 传递List&lt;CountryItem&gt;,因为Android Studio 不允许我编译项目(实体实现接口、编译错误等)。我想要实现的是以更好的方式映射Either 结果。

【问题讨论】:

    标签: android kotlin either


    【解决方案1】:

    尝试使用 Kotlin 的 Result

    因此,在您的情况下,您可以编写如下内容:

    return result.mapCatching { list: List<Country> -> /*return here List<CountryItem>>*/ }
    

    用于检查结果调用:

    result.fold(
        onSuccess = {...},
        onFailure = {...}
    )
    

    为了调用构造函数,您应该调用Result.success(T)Result.failure(Throwable)

    不幸的是,您还需要禁止使用返回类型警告How to

    【讨论】:

    • 为什么Result优于Either
    • @IgorGanapolsky Result 是标准库的一部分,Either 不是。
    【解决方案2】:

    您可以通过检查Either 的类型并直接访问该值来简化。在你的情况下:

    通过result.a访问Left -> Failure

    通过result.b访问Right -> List&lt;Country&gt;

    例如:

    when (result) {
        is Either.Left -> {
            val failure: Failure = result.b
            ...
        }
        is Either.Right -> {
            val countries: List<Country> = result.b
            ...
        }
    }
    

    另一种方法是使用either() 函数(通常称为fold()):

    result.either(
        { /** called when Left; it == Failure */ }, 
        { /** called when Right; it == List<Country> */ }
    )
    

    【讨论】:

    • 你能澄清一下如何用折叠替换任何一个类吗?
    【解决方案3】:

    假设您的 Country 类定义如下:

    data class Country(val name: String) {}
    

    你的CountryItem 类定义如下:

    data class CountryItem(private val name: String, private val population: Int) {}
    

    和你的 CountryLocalDataSource 类和 getCountries() 这样的方法:

    class DataSource {
        suspend fun getCountries(): Either<Exception, List<Country>> {
            return Either.Right(listOf(Country("USA"), Country("France")))
            //return Either.Left(Exception("Error!!!"))
        }
    }
    

    那么你的问题的答案是:

    suspend fun getCountryItems(): Either<Exception, List<CountryItem>> {        
        when (val countriesOrFail = DataSource().getCountries()) {
            is Either.Left -> {
                return Either.Left(countriesOrFail.a)
            }
            is Either.Right -> {
                val countryItems = countriesOrFail.b.map {
                    CountryItem(it.name, 1000)
                }
                return Either.Right(countryItems)
            }        
        }    
    }
    

    打电话给你的getCountryItems(),下面是一个例子:

    suspend fun main() {
        when (val countriesOrFail = getCountryItems()) {
            is Either.Left -> {
                println(countriesOrFail.a.message)
            }
            is Either.Right -> {
                println(countriesOrFail.b)       
            }
        }
    }
    

    这是 Playground 中的示例代码:https://pl.kotl.in/iiSrkv3QJ

    关于地图功能的说明:

    我猜您实际上并不需要结果是 MutableList&lt;CountryItem&gt;,但您必须定义它,因为您想在遍历输入列表 List&lt;Country&gt; 时添加一个元素。

    可能情况如下:如果您有一个 List&lt;Country&gt; 与示例中的 2 个元素类似,并且您想要映射以使结果变为具有 2 个对应元素的 List&lt;CountryItem&gt;,那么您不需要需要在fun 中调用forEach,然后将其传递给高阶函数map。但这可能是一个全新的问题。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-10-28
      • 2023-02-09
      • 2015-12-08
      • 2015-12-08
      • 2022-06-22
      • 1970-01-01
      • 1970-01-01
      • 2015-02-14
      相关资源
      最近更新 更多