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). Simply create a Kotlin file (named ExtensionFunctions.kt or whatever you want) with the following code - that's it!

Code: Select all
package <YOUR PACKAGE>

import android.content.ContentValues
import android.database.Cursor
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
import java.time.format.DateTimeFormatter

object DateTimeUtil {
    // ---------------------------------------------------------------------------------------------
    // ------- Converts date / time related types to String and vice versa
    // ---------------------------------------------------------------------------------------------
    const val DB_DATE_FORMAT = "YYYY-MM-DD"
    const val DB_TIME_FORMAT = "HH:MM:SS.SSS"
    const val DB_DATETIME_FORMAT = "$DB_DATE_FORMAT $DB_TIME_FORMAT"
    val dbDateFormatter = DateTimeFormatter.ofPattern(DB_DATE_FORMAT)
    val dbTimeFormatter = DateTimeFormatter.ofPattern(DB_TIME_FORMAT)
    val dbDateTimeFormatter = DateTimeFormatter.ofPattern(DB_DATETIME_FORMAT)

    fun localDateFromString(s : String?) : LocalDate? {
        if (s == null) return null
        return LocalDate.parse(s, dbDateFormatter)
    }

    fun stringFromLocalDate(localDate : LocalDate?) : String? {
        if (localDate == null) return null
        return localDate.toString() // outputs String in format "uuuu-MM-dd" - see https://bit.ly/2sVhwxC
    }

    fun localTimeFromString(s : String?) : LocalTime? {
        if (s == null) return null
        return LocalTime.parse(s, dbTimeFormatter)
    }

    fun stringFromLocalTime(localTime : LocalTime?) : String? {
        if (localTime == null) return null
        return localTime.toString() //  outputs String in format "HH:mm:ss.SSS" - see https://bit.ly/2YvBieH
    }

    fun localDateTimeFromString(s : String?) : LocalDateTime? {
        if (s == null) return null
        return LocalDateTime.parse(s, dbDateTimeFormatter)
    }

    fun stringFromLocalDateTime(localDateTime : LocalDateTime?) : String? {
        if (localDateTime == null) return null
        return localDateTime.toString() // outputs String in format "uuuu-MM-dd'T'HH:mm:ss.SSS" - see https://bit.ly/2YpZKy6
    }
}

// -------------------------------------------------------------------------------------------------
// ------- Extend ContentValues: add putters for additional datatypes
// -------------------------------------------------------------------------------------------------

fun ContentValues.put(key : String, value : LocalDate?) { this.put(key, DateTimeUtil.stringFromLocalDate(value)) }
fun ContentValues.put(key : String, value : LocalTime?) { this.put(key, DateTimeUtil.stringFromLocalTime(value)) }
fun ContentValues.put(key : String, value : LocalDateTime?) { this.put(key, DateTimeUtil.stringFromLocalDateTime(value)) }

// -------------------------------------------------------------------------------------------------
// ------- Extend Cursor: add getters by col name (rather than col index) + for additional datatypes
// -------------------------------------------------------------------------------------------------

fun Cursor.getShort(columnName : String) : Short? { return this.getShort(this.getColumnIndex(columnName)) }
fun Cursor.getInt(columnName : String) : Int? { return this.getInt(this.getColumnIndex(columnName)) }
fun Cursor.getLong(columnName : String) : Long? { return this.getLong(this.getColumnIndex(columnName)) }
fun Cursor.getFloat(columnName : String) : Float? { return this.getFloat(this.getColumnIndex(columnName)) }
fun Cursor.getDouble(columnName : String) : Double? { return this.getDouble(this.getColumnIndex(columnName)) }
fun Cursor.getString(columnName : String) : String? { return this.getString(this.getColumnIndex(columnName)) }
fun Cursor.getBlob(columnName : String) : ByteArray? { return 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 DateTimeUtil.localDateFromString(s)
}

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

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



IF you want to ((lose weight) OR (have unbelievable (brain function AND mental clarity))) THEN click:




Traffic Counter

free counters

eXTReMe Tracker