4

I'm learning the sample code about Anko at Kotlin for Android Developers (the book) https://github.com/antoniolg/Kotlin-for-Android-Developers

The Method 1 is from sample code and override parseList ,but it's hard to understand.

So I try to use the Method 2 instead of the Method 1, the Method 2 use original parseList function, but I get blank record when I use the Method 2, what error do I made in the Method 2

class DayForecast(var map: MutableMap<String, Any?>) {
  var _id: Long by map
  var date: Long by map
  var description: String by map
  var high: Int by map
  var low: Int by map
  var iconUrl: String by map
  var cityId: Long by map

  constructor(date: Long, description: String, high: Int, low: Int,
              iconUrl: String, cityId: Long) : this(HashMap()) {
    this.date = date
    this.description = description
    this.high = high
    this.low = low
    this.iconUrl = iconUrl
    this.cityId = cityId
  }
}

Method 1

override fun requestForecastByZipCode(zipCode: Long, date: Long) =
  forecastDbHelper.use {
    val dailyRequest = "${DayForecastTable.CITY_ID} = ? AND ${DayForecastTable.DATE} >= ?"
    val dailyForecast = select(DayForecastTable.NAME)
          .whereSimple(dailyRequest, zipCode.toString(), date.toString())
          .parseList { DayForecast(HashMap(it)) }
    /* common code block */
  }

fun <T : Any> SelectQueryBuilder.parseList(parser: (Map<String, Any?>) -> T):
        List<T> = parseList(object : MapRowParser<T> {
  override fun parseRow(columns: Map<String, Any?>): T = parser(columns)
})

Method 2

override fun requestForecastByZipCode(zipCode: Long, date: Long) = 
  forecastDbHelper.use {
    val dailyRequest = "${DayForecastTable.CITY_ID} = ? AND ${DayForecastTable.DATE} >= ?"
    val dailyForecast = select(DayForecastTable.NAME)
          .whereSimple(dailyRequest, zipCode.toString(), date.toString())
          .exec { parseList(classParser<DayForecast>()) }
    /* common code block */
  }
Zoe
  • 27,060
  • 21
  • 118
  • 148
HelloCW
  • 843
  • 22
  • 125
  • 310

1 Answers1

2

I really do think you should stick to using the 'method 1' approach, it's a lot easier once you realise what Kotlin is letting you do. As I don't know how much you know about Kotlin, I'll try to cover this completely.

The existing class SelectQueryBuilder has (I presume) a function called parseList, that existing function takes a MapRowParser<T>. The MapRowParser<T> has a function parseRow that takes a Map<String, Any?> and returns a T.

In the old Java way of working, you would derive from MapRowParser<T> and would override parseRow so that it does the conversion you want; converting the Map<String, Any?> into a DayForecast (the generic T would now have a type). An instance of this derived class is passed into the existing parseList function. Your derived class would look something like

class MapToDayForecastRowParser extends MapRowParser<DayForecast> {
  @Override public DayForecast parseRow(Map<String, Object> map) {
    // Note that Java's "Object" is more or less Kotlin's "Any?"
    return new DayForecast(map); // Might need to convert the map type btw
  }
}

The extension method is making it really easy to wrap/hide/abstract that creation of the derived class. The extension method take a lambda, that is, you have to parse into the new parseList method a block of code that takes a Map<String, Any?> and returns T (this is what DayForecast(HashMap(it)) is doing, the it is an automatically named variable that is the Map. The extension method then calls the existing parseList method, parsing in an anonymous class that it creates itself. That means that ever use of this extension method creates a new anonymous class, but the Kotlin compiler handles this very well.

One part that did confuse me at first is the way Kotlin handles the anonymous class.

// Java
new MapRowParser<T>() {
  @Override public T parseRow(Map<String, Object>) {
     /* Map to T logic */
  }
}
// Kotlin
object : MapRowParser<T> {
 override fun parseRow(columns: Map<String, Any?>): T = parser(columns)
}

Kotlin also makes it very easy to handle the 'lambda'. It is parsed into the extension method as parser and then set as the implementation of our anonymous class parseRow function. You can also reuse them if you so wished, your so if you need to do the same sort of parsing in lots of places, you can use a named function instead.

The great advantage to this new Kotlin way is that it's very easy focus on what you want to do. With that extension method in place, it's very quick to re-use it so that in another query you can do parseList{ it.getOrDefault("name", "unkown_user") }. You can now easily work on just thinking "If each row is a map, how do I convert that down to a value I want?".

thecoshman
  • 8,394
  • 8
  • 55
  • 77