Kotlin: Extend Cursor interface and ContentValues class


Topics interesting only for Kotlin / Android / SQLite programmers

Link to this posting

Postby Ursego » 09 Dec 2019, 11:16

The problems:

1. The ContentValues class has putters for most types, but not for LocalDate, LocalTime and LocalDateTime. So, when we send ContentValues to methods insert() and update() of SQLiteDatabase, we need to worry about correct packing of these types into String considering formats.

2. The Cursor interface doesn't have getters for LocalDate, LocalTime and LocalDateTime. So, reading them from a cursor, we need to worry about correct unpacking of these types from String considering formats.

3. Getters of Cursor (such as getInt, getString etc.) accept column index rather than column name, so we write extra code, which is against the Kotlin't philosophy):

Code: Select all
emp.id = cursor.getInt(cursor.getColumnIndex(DbColumn.ID))
emp.lastName= cursor.getString(cursor.getColumnIndex(DbColumn.LAST_NAME))

rather than

Code: Select all
emp.id = cursor.getInt(DbColumn.ID)
emp.lastName= cursor.getString(DbColumn.LAST_NAME)

4. Cursor doesn't have a getter for Boolean, so we need to extract it from Int (as it is stored in SQLite):

Code: Select all
emp.isActive = (cursor.getInt(cursor.getColumnIndex(DbColumn.IS_ACTIVE)) == 1) // no exception if it was mistakenly inserted as 625!

rather than

Code: Select all
emp.isActive = cursor.getBoolean(DbColumn.IS_ACTIVE)

HOW TO SOLVE ALL THESE PROBLEMS?

Fortunately, we can add extended functions in Kotlin (even functions with implementation to interfaces).

STEPS:

@ Create object Chronos as described here.

@ In "util" package, created in first step, create a Kotlin file named ExtensionFunctions with the following code (just after the "package" statement):

Code: Select all
import android.content.ContentValues
import android.database.Cursor
import java.time.LocalDate
import java.time.LocalTime
import java.time.LocalDateTime

// -----------------------------------------------------------------------------------------------------------------
// ------- Extend ContentValues: add putters for additional data types (http://code.intfast.ca/viewtopic.php?t=814):
// -----------------------------------------------------------------------------------------------------------------

fun ContentValues.put(key: String, value : LocalDate?) = this.put(key, Chronos.toString(value))
fun ContentValues.put(key: String, value : LocalTime?) = this.put(key, Chronos.toString(value))
fun ContentValues.put(key: String, value : LocalDateTime?) = this.put(key, Chronos.toString(value))

// -----------------------------------------------------------------------------------------------------------------
// ------- Extend Cursor: add getters for additional data types, and allow to get by column name
// ------- rather than column index (http://code.intfast.ca/viewtopic.php?t=814):
// -----------------------------------------------------------------------------------------------------------------

fun Cursor.getShort(columnName: String): Short? = this.getShort(this.getColumnIndex(columnName))
fun Cursor.getInt(columnName: String): Int? = this.getInt(this.getColumnIndex(columnName))
fun Cursor.getLong(columnName: String): Long? = this.getLong(this.getColumnIndex(columnName))
fun Cursor.getFloat(columnName: String): Float? = this.getFloat(this.getColumnIndex(columnName))
fun Cursor.getDouble(columnName: String): Double? = this.getDouble(this.getColumnIndex(columnName))
fun Cursor.getString(columnName: String): String? = this.getString(this.getColumnIndex(columnName))
fun Cursor.getBlob(columnName: String): ByteArray? = this.getBlob(this.getColumnIndex(columnName))

fun Cursor.getBoolean(columnName: String): Boolean {
    val i = this.getInt(this.getColumnIndex(columnName))
    when (i) {
        1 -> return true
        0 -> return false
    }
    throw Exception("Value of field $columnName is $i. To be treated as Boolean, it must be 0 or 1.")
}

fun Cursor.getLocalDate(columnName: String): LocalDate? {
    val s = this.getString(this.getColumnIndex(columnName)) ?: return null
    return Chronos.toLocalDate(s)
}

fun Cursor.getLocalTime(columnName: String): LocalTime? {
    val s = this.getString(this.getColumnIndex(columnName)) ?: return null
    return Chronos.toLocalTime(s)
}

fun Cursor.getLocalDateTime(columnName: String): LocalDateTime? {
    val s = this.getString(this.getColumnIndex(columnName)) ?: return null
    return Chronos.toLocalDateTime(s)
}
User avatar
Ursego
Site Admin
 
Posts: 143
Joined: 19 Feb 2013, 20:33



Ketones are a more high-octane fuel for your brain than glucose. Become a biohacker and upgrade yourself to version 2.0!



cron
Traffic Counter

eXTReMe Tracker