今日のkotlin: Cursorまわりの話
日常的に書いてて「お、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みたいなので、必ず中は呼びます、というモディファイヤは無いものか?と少し公式ドキュメントを見たが無さそう。まぁいいか。