Configurar o SQLite para KMP

A biblioteca androidx.sqlite contém interfaces abstratas com implementações básicas que podem ser usadas para criar bibliotecas que acessam o SQLite. É recomendado usar a biblioteca Room, que fornece uma camada de abstração para permitir um acesso mais robusto ao banco de dados, aproveitando toda a capacidade do SQLite.

Configurar dependências

Para configurar o SQLite no seu projeto do KMP, adicione as dependências dos artefatos no arquivo build.gradle.kts do seu módulo:

[versions]
sqlite = "2.7.1"

[libraries]
# The SQLite Driver interfaces
androidx-sqlite = { module = "androidx.sqlite:sqlite", version.ref = "sqlite" }

# The bundled SQLite driver implementation
androidx-sqlite-bundled = { module = "androidx.sqlite:sqlite-bundled", version.ref = "sqlite" }

[plugins]
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }

APIs de driver do SQLite

Os grupos de bibliotecas androidx.sqlite oferecem APIs de baixo nível para comunicação com a biblioteca SQLite incluída na biblioteca ao usar androidx.sqlite:sqlite-bundled ou na plataforma host, como Android ou iOS, ao usar androidx.sqlite:sqlite-framework. As APIs seguem de perto a funcionalidade principal da API SQLite C.

Há três interfaces principais:

O exemplo a seguir mostra as principais APIs:

fun main() {
    val databaseConnection = BundledSQLiteDriver().open("todos.db")
    databaseConnection.execSQL(
        "CREATE TABLE IF NOT EXISTS Todo (id INTEGER PRIMARY KEY, content TEXT)"
    )
    databaseConnection.prepare(
        "INSERT OR IGNORE INTO Todo (id, content) VALUES (? ,?)"
    ).use { stmt ->
        stmt.bindInt(index = 1, value = 1)
        stmt.bindText(index = 2, value = "Try Room in the KMP project.")
        stmt.step()
    }
    databaseConnection.prepare("SELECT content FROM Todo").use { stmt ->
        while (stmt.step()) {
            println("Action item: ${stmt.getText(0)}")
        }
    }
    databaseConnection.close()
}

Semelhante às APIs C do SQLite, o padrão de uso comum consiste nestas etapas:

  1. Abra uma conexão de banco de dados usando a implementação SQLiteDriver instanciada.
  2. Prepare uma instrução SQL usando SQLiteConnection.prepare.
  3. Execute um SQLiteStatement fazendo o seguinte:
    1. Opcional: vincule argumentos usando as funções bind*.
    2. Itere sobre o conjunto de resultados usando a função step.
    3. Leia as colunas do conjunto de resultados usando as funções get*.

Implementações de driver

A tabela a seguir resume as implementações de driver disponíveis:

Nome da classe

Artefato

Plataformas compatíveis

AndroidSQLiteDriver androidx.sqlite:sqlite-framework

Android

NativeSQLiteDriver androidx.sqlite:sqlite-framework

iOS, Mac e Linux

BundledSQLiteDriver androidx.sqlite:sqlite-bundled

Android, iOS, Mac, Linux e JVM (computador)

WebWorkerSQLiteDriver androidx.sqlite:sqlite-web

JavaScript e WebAssembly (WasmJS)

A implementação recomendada é BundledSQLiteDriver, disponível em androidx.sqlite:sqlite-bundled. Ela inclui a biblioteca SQLite compilada da origem, oferecendo a versão mais atualizada e consistência em todas as plataformas KMP compatíveis.

Driver do SQLite e Room

As APIs de driver são úteis para interações de baixo nível com um banco de dados SQLite. Para uma biblioteca com muitos recursos que oferece acesso mais robusto ao SQLite, recomendamos o Room.

Um RoomDatabase depende de um SQLiteDriver para realizar operações de banco de dados, e você precisa configurar uma implementação usando RoomDatabase.Builder.setDriver. O Room oferece RoomDatabase.useReaderConnection e RoomDatabase.useWriterConnection para um acesso mais direto às conexões de banco de dados gerenciadas.

Migrar para o Kotlin Multiplatform

É necessário migrar qualquer uso de componentes de baixo nível da API SQLite de suporte, como a interface SupportSQLiteDatabase, para os componentes equivalentes do driver do SQLite.

Kotlin Multiplatform

Realizar uma transação usando SQLiteConnection de baixo nível

val connection: SQLiteConnection = BundledSQLiteDriver().open("todos.db")
connection.execSQL("BEGIN IMMEDIATE TRANSACTION")
try {
    // perform database operations in transaction.
    connection.execSQL("END TRANSACTION")
} catch (t: Throwable) {
    connection.execSQL("ROLLBACK TRANSACTION")
}

Executar uma consulta sem resultado

val connection: SQLiteConnection = BundledSQLiteDriver().open("todos.db")
connection.execSQL("ALTER TABLE ...")

Executar uma consulta com resultado, mas sem argumentos

val connection: SQLiteConnection = BundledSQLiteDriver().open("todos.db")
connection.prepare("SELECT * FROM Pet").use { statement ->
    while (statement.step()) {
        // read columns.
        statement.getInt(0)
        statement.getText(1)
    }
}

Executar uma consulta com resultado e argumentos

connection.prepare("SELECT * FROM Pet WHERE id = ?").use { statement ->
    statement.bindInt(1, id)
    if (statement.step()) {
        // row found, read columns.
    } else {
        // row not found.
    }
}

Somente para Android

Realizar uma transação usando o SupportSQLiteDatabase

val database: SupportSQLiteDatabase = ...
database.beginTransaction()
try {
  // perform database operations in transaction
  database.setTransactionSuccessful()
} finally {
  database.endTransaction()
}

Executar uma consulta sem resultado

val database: SupportSQLiteDatabase = ...
database.execSQL("ALTER TABLE ...")

Executar uma consulta com resultado, mas sem argumentos

val database: SupportSQLiteDatabase = ...
database.query("SELECT * FROM Pet").use { cursor ->
  while (cursor.moveToNext()) {
    // read columns
    cursor.getInt(0)
    cursor.getString(1)
  }
}

Executar uma consulta com resultado e argumentos

database.query("SELECT * FROM Pet WHERE id = ?", id).use { cursor ->
  if (cursor.moveToNext()) {
    // row found, read columns
  } else {
    // row not found
  }
}