准备请求

因为我们需要知道哪一个item我们要在详情界面中显示出来,所以逻辑告诉我们需要发送一个天气预报的id到详情界面。所以domain model需要一个新的id属性:

  1. data class Forecast(val id: Long, val date: Long, val description: String,
  2. val high: Int, val low: Int, val iconUrl: String)

ForecastProvider也需要一个新的函数,它返回通过id请求后的结果。DetailActivity将需要通过接收到的id来执行请求获取天气预报数据。因为所有的请求都会迭代所有的数据源并且返回第一个非null的结果,我们可以抽取并定义一个新的函数:

  1. private fun <T : Any> requestToSources(f: (ForecastDataSource) -> T?): T
  2. = sources.firstResult { f(it) }

这个函数使用一个非null类型作为范型。它会接收一个函数,并返回一个可null的对象。其中这个接收的函数接收一个ForecastDataSource,并返回一个可null范型的对象。我们可以重写上一个请求并如下写一个新的:

  1. fun requestByZipCode(zipCode: Long, days: Int): ForecastList = requestToSources {
  2. val res = it.requestForecastByZipCode(zipCode, todayTimeSpan())
  3. if (res != null && res.size() >= days) res else null
  4. }
  5. fun requestForecast(id: Long): Forecast = requestToSources {
  6. it.requestDayForecast(id)
  7. }

现在数据源需要去实现一个新的函数:

  1. fun requestDayForecast(id: Long): Forecast?

ForcastDb将总是会拿到所需的在上一次请求被缓存的值,所以我们可以通过这种方式去获取它:

  1. override fun requestDayForecast(id: Long): Forecast? = forecastDbHelper.use {
  2. val forecast = select(DayForecastTable.NAME).byId(id).
  3. parseOpt { DayForecast(HashMap(it)) }
  4. if (forecast != null) dataMapper.convertDayToDomain(forecast) else null
  5. }

select从查询与之前的非常相似。我创建了另一个名为byId的工具函数,因为通过id来请求是很通用的,像这样使用一个函数可以简化处理过程也更具可读性。函数的实现也是相当简单:

  1. fun SelectQueryBuilder.byId(id: Long): SelectQueryBuilder
  2. = whereSimple("_id = ?", id.toString())

它只是使用了whereSimple函数实现使用_id字段来查询数据。这个函数相当普通,但是如你所见,你可以根据你数据库结构的需要来创建需要的扩展函数,它可以大量地简化你代码的可读性。DataMapper有一些不值得一提的轻微改变。你可以通过代码库来查看它们。

另一方面,ForecastServer将不会再使用,因为信息总是会被缓存在数据库中。我们可以在一些奇怪的场景下实现一些代码保护,但是我们在这个例子中没有做任何处理,所以如果发生它也会只是抛出一个异常:

  1. override fun requestDayForecast(id: Long): Forecast?
  2. = throw UnsupportedOperationException()

trythrow是表达式

在Kotlin中,几乎一切都是表达式,也就是说一切都会返回一个值。这在函数式编程中是非常重要的,当你使用try-catch处理边界的问题或者当抛出异常的时候。比如,在上一个例子中,我们可以给结果分配一个exception就算他们不是相同的类型,而不是必须要去创建一个完整的代码块。当我们需要在一个when分支中抛出一个exception的时候也是非常有用:

  1. val x = when(y){
  2. in 0..10 -> 1
  3. in 11..20 -> 2
  4. else -> throw Exception("Invalid")
  5. }

try-catch中也是一样,我们可以根据try的结果分配一个值:

  1. val x = try{ doSomething() }catch{ null }

最后一件我们需要做的事就是在新的activity中创建一个command来执行请求:

  1. class RequestDayForecastCommand(
  2. val id: Long,
  3. val forecastProvider: ForecastProvider = ForecastProvider()) :
  4. Command<Forecast> {
  5. override fun execute() = forecastProvider.requestForecast(id)
  6. }

请求返回一个将用于activity绘制UI的Forecast结果。