日常的に書いてて「お、kotlinちょっといいな」と思うような時に、それをあんまり共有している人を見かけない気がする。

という事でまずは自分がそう思った物を書いて行こう、という話。 別に公式ドキュメント以上の情報はありません。

SelectBuilderは自作のを使ってる

SQLite周辺は、結構いろんなライブラリを評価してきたが、結局自分で書くのが一番、という結論になりつつある。

ちょっと手抜きだが以下みたいなのを作っていて、

class SelectBuilder(val tableName: String) {
    var distinct = false
    var columns = arrayOf<String>()
    var selection : String? = null
    var selectionArgs = arrayOf<String>()
    var groupBy : String? = null
    var having : String? = null
    var orderBy : String? = null

    var limit : String? = null

    fun select(vararg fields: String) {
        columns = arrayOf(*fields)
    }

    fun order(sentence: String) {
        orderBy = sentence
    }

    fun where(whereSentence: String, vararg args: String) {
        selection = whereSentence
        selectionArgs = arrayOf(*args)
    }


    fun exec(db: SQLiteDatabase) : Cursor {
        val columnsArg = if(columns.isEmpty()) null else columns
        val selectionArgsArg = if(selectionArgs.isEmpty()) null else selectionArgs

        return db.query(distinct, tableName, columnsArg, selection, selectionArgsArg, groupBy, having, orderBy, limit)
    }
}

fun DatabaseHolder.query(tableName: String, body: SelectBuilder.()->Unit) : Cursor{
    val builder = SelectBuilder(tableName)
    builder.body()
    return builder.exec(this.database)
}

こんな風に使う。

    val newCursor = async(Dispatchers.IO) {
        database.query(DatabaseHolder.ENTRY_TABLE_NAME) {
            select("_id", "BODY", "DATE")
            val word = v.text.toString()
            if(!word.isEmpty())
                where("BODY like ?", "%"+v.text.toString()+"%")
            order("DATE DESC, _id DESC")
        }
    }

orderとかがtype safeじゃないのがダサいけど、実用上はそんなは困らない。 そのうちDESCとASCなextensionを足してもいいが、そんな困ってないからまぁいいか、と。

これはほぼ汎用なので汎用なのがあっても良い気はするんだが…

世の中のライブラリはすぐ下のレイヤーを隠そうとして、あんまりListViewとかで使いやすくなかったり、無駄なものがいろいろついてきたりする。 この程度100行もかからないので、自力でいいか、という気になった。

Cursorをcloseしつつ何かしたい

で、CRUDアプリ作ってて特定のidをmaster-detail的に表示したい、となった。 この場合一件だけqueryして結果が欲しい。 今回はテーブルにはidと日付と文字列の三つのカラムのみ。

日付と文字列はとりあえずPairでいいか。

cursorは上のqueryで取れる。こんな感じ。

fun DatabaseHolder.getEntry(id: Long): Pair<Long, String> {
    val cursor = query(DatabaseHolder.ENTRY_TABLE_NAME) {
        where("_id=?", id.toString())
    }
    // ここでcursorから取り出してreturnしたい。

取り出した後にcloseしなきゃいけないが、そういうのは嫌だよな、と以下のようなものを書こうとした。

inline fun Cursor.withClose(body: Cursor.()->Unit) : Unit{
    body()
    this.close()
}

これでこう書けばいいか?と思ったが、、、

fun DatabaseHolder.getEntry(id: Long): Pair<Long, String> {
    val cursor = query(DatabaseHolder.ENTRY_TABLE_NAME) {
        where("_id=?", id.toString())
    }.withClose {
        moveToFirst()
        return Pair(getLong(1), getString(2))
    }

withCloseの次にreturnが無いぜ、とか言われる。withCloseの中は必ず呼ばれて、これでreturnしてればそれでOKなんだけど、それをうまく表現する方法が分からなかった。

仕方ないのでwithCloseを結果を返すように修正した。

inline fun <reified T> Cursor.withClose(body: Cursor.()->T) : T{
    val res = body()
    this.close()
    return res
}

こんな感じで使う。ちょっとださい。

fun DatabaseHolder.getEntry(id: Long): Pair<Long, String> {
    return query(DatabaseHolder.ENTRY_TABLE_NAME) {
        where("_id=?", id.toString())
    }.withClose {
        moveToFirst()
        Pair(getLong(1), getString(2))
    }

うーん、withCloseみたいなので、必ず中は呼びます、というモディファイヤは無いものか?と少し公式ドキュメントを見たが無さそう。まぁいいか。