Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings
This repository was archived by the owner on Jul 13, 2020. It is now read-only.
/ankoPublic archive

Anko SQLite

Paulo Salvatore edited this pageMar 28, 2019 ·26 revisions

Have you ever been tired of parsing SQLite query results using Android cursors? You have to write lots of boilerplate code just to parse query result rows, and enclose it in countlesstry..finally blocks to properly close all opened resources.

Anko provides lots of extension functions to simplify working with SQLite databases.

Contents

Using Anko SQLite in your project

Add theanko-sqlite dependency to yourbuild.gradle:

dependencies {    implementation"org.jetbrains.anko:anko-sqlite:$anko_version"}

Accessing the database

If you useSQLiteOpenHelper, you generally callgetReadableDatabase() orgetWritableDatabase() (result is actually the same in production code), but then you must be sure to call theclose() method on the receivedSQLiteDatabase. Also, you have to cache the helper class somewhere, and if you use it from several threads, you must be aware of the concurrent access. All this is pretty tough. That is why Android developers are not really keen on default SQLite API and prefer to use fairly expensive wrappers such as ORMs instead.

Anko provides a special classManagedSQLiteOpenHelper that seamlessly replaces the default one. Here's how you can use it:

classMyDatabaseOpenHelper private constructor(ctx:Context) : ManagedSQLiteOpenHelper(ctx,"MyDatabase",null,1) {init {        instance=this    }companionobject {privatevar instance:MyDatabaseOpenHelper?=null        @SynchronizedfungetInstance(ctx:Context)= instance?:MyDatabaseOpenHelper(ctx.applicationContext)    }overridefunonCreate(db:SQLiteDatabase) {// Here you create tables        db.createTable("Customer",true,"id" toINTEGER+PRIMARY_KEY+UNIQUE,"name" toTEXT,"photo" toBLOB)    }overridefunonUpgrade(db:SQLiteDatabase,oldVersion:Int,newVersion:Int) {// Here you can upgrade tables, as usual        db.dropTable("User",true)    }}// Access property for ContextvalContext.database:MyDatabaseOpenHelper    get()=MyDatabaseOpenHelper.getInstance(this)

So what's the sense? Instead of enclosing your code intotry blocks, now you can just write this:

database.use {// `this` is a SQLiteDatabase instance}

The database will definitely be closed after executing all code inside{}.

Asynchronous call example:

classSomeActivity :Activity() {privatefunloadAsync() {        async(UI) {val result= bg {                 database.use {... }            }            loadComplete(result)        }    }}
🐧These and all methods mentioned below may throwSQLiteException. You have to handle it by yourself because it would be unreasonable for Anko to pretend that errors don't occur.

Creating and dropping tables

With Anko you can easily create new tables and drop existing ones. The syntax is straightforward.

database.use {    createTable("Customer",true,"id" toINTEGER+PRIMARY_KEY+UNIQUE,"name" toTEXT,"photo" toBLOB)}

In SQLite, there are five main types:NULL,INTEGER,REAL,TEXT andBLOB. But each column may have some modifiers likePRIMARY KEY orUNIQUE. You can append such modifiers with "adding" them to the primary type name.

To drop a table, use thedropTable function:

dropTable("User",true)

Inserting data

Usually, you need aContentValues instance to insert a row into the table. Here is an example:

val values=ContentValues()values.put("id",5)values.put("name","John Smith")values.put("email","user@domain.org")db.insert("User",null, values)

Anko lets you eliminate such ceremonies by passing values directly as arguments for theinsert() function:

// Where db is an SQLiteDatabase// eg: val db = database.writeableDatabasedb.insert("User","id" to42,"name" to"John","email" to"user@domain.org")

or from withindatabase.use as:

database.use {    insert("User","id" to42,"name" to"John","email" to"user@domain.org"   )}

Please note, in the above exampledatabase is a database helper instance, anddb is aSQLiteDatabase object

FunctionsinsertOrThrow(),replace(),replaceOrThrow() also exist and have the similar semantics.

Querying data

Anko provides a convenient query builder. It may be created withdb.select(tableName, vararg columns) wheredb is an instance ofSQLiteDatabase.

MethodDescription
column(String)Add a column to select query
distinct(Boolean)Distinct query
whereArgs(String)Specify raw Stringwhere query
whereArgs(String, args)Specify awhere query with arguments
whereSimple(String, args)Specify awhere query with? mark arguments
orderBy(String, [ASC/DESC])Order by this column
groupBy(String)Group by this column
limit(count: Int)Limit query result row count
limit(offset: Int, count: Int)Limit query result row count with an offset
having(String)Specify rawhaving expression
having(String, args)Specify ahaving expression with arguments

Functions marked with ⭐ parse its arguments in a special way. They allow you to provide values in any order and support escaping seamlessly.

db.select("User","name")    .whereArgs("(_id > {userId}) and (name = {userName})","userName" to"John","userId" to42)

Here,{userId} part will be replaced with42 and{userName} — with'John'. The value will be escaped if its type is not numeric (Int,Float etc.) orBoolean. For any other types,toString() representation will be used.

whereSimple function accepts arguments ofString type. It works the same asquery() fromSQLiteDatabase (question marks? will be replaced with actual values from arguments).

How can we execute the query? Using theexec() function. It accepts an extension function with the type ofCursor.() -> T. It simply launches the received extension function and then closesCursor so you don't need to do it by yourself:

db.select("User","email").exec {// Doing some stuff with emails}

Parsing query results

So we have someCursor, and how can we parse it into regular classes? Anko provides functionsparseSingle,parseOpt andparseList to do it much more easily.

MethodDescription
parseSingle(rowParser): TParse exactly one row
parseOpt(rowParser): T?Parse zero or one row
parseList(rowParser): List<T>Parse zero or more rows

Note thatparseSingle() andparseOpt() will throw an exception if the received Cursor contains more than one row.

Now the question is: what isrowParser? Well, each function supports two different types of parsers:RowParser andMapRowParser:

interfaceRowParser<T> {funparseRow(columns:Array<Any>):T}interfaceMapRowParser<T> {funparseRow(columns:Map<String,Any>):T}

If you want to write your query in a very efficient way, use RowParser (but then you must know the index of each column).parseRow accepts a list ofAny (the type ofAny could practically be nothing butLong,Double,String orByteArray).MapRowParser, on the other hand, lets you get row values by using column names.

Anko already has parsers for simple single-column rows:

  • ShortParser
  • IntParser
  • LongParser
  • FloatParser
  • DoubleParser
  • StringParser
  • BlobParser

Also, you can create a row parser from the class constructor. Assuming you have a class:

data classPerson(valfirstName:String,vallastName:String,valage:Int)

The parser will be as simple as:

val rowParser= classParser<Person>()

For now, Ankodoes not support creating such parsers if the primary constructor has optional parameters. Also, note that constructor will be invoked using Java Reflection so writing a customRowParser is more reasonable for huge data sets.

If you are using Ankodb.select() builder, you can directly callparseSingle,parseOpt orparseList on it and pass an appropriate parser.

Custom row parsers

For instance, let's make a new parser for columns(Int, String, String). The most naive way to do so is:

classMyRowParser :RowParser<Triple<Int,String,String>> {overridefunparseRow(columns:Array<Any>):Triple<Int,String,String> {returnTriple(columns[0]asInt, columns[1]asString, columns[2]asString)    }}

Well, now we have three explicit casts in our code. Let's get rid of them by using therowParser function:

val parser= rowParser { id:Int, name:String, email:String->Triple(id, name, email)}

And that's it!rowParser makes all casts under the hood and you can name the lambda parameters as you want.

Cursor streams

Anko provides a way to access SQLiteCursor in a functional way. Just callcursor.asSequence() orcursor.asMapSequence() extension functions to get a sequence of rows. Do not forget to close theCursor :)

Updating values

Let's give a new name to one of our users:

update("User","name" to"Alice")    .where("_id = {userId}","userId" to42)    .exec()

Update also has awhereSimple() method in case you want to provide the query in a traditional way:

update("User","name" to"Alice")    .`whereSimple`("_id = ?",42)    .exec()

Deleting Data

Let's delete a row (notice the delete method doesn't have anywhereSimple() method; instead you supply the query directly in the arguments):

val numRowsDeleted= delete("User","_id = {userID}","userID" to37)

Transactions

There is a special function calledtransaction() which allows you to enclose several database operations in a single SQLite transaction.

transaction {// Your transaction code}

The transaction will be marked as successful if no exception was thrown inside the{} block.

🐧If you want to abort a transaction for some reason, just throwTransactionAbortException. You don't need to handle this exception by yourself in this case.
Clone this wiki locally

[8]ページ先頭

©2009-2025 Movatter.jp