Konfigurowanie SQLite na potrzeby KMP

Biblioteka androidx.sqlite zawiera abstrakcyjne interfejsy oraz podstawowe implementacje, których można używać do tworzenia własnych bibliotek, które mają dostęp do SQLite. Warto rozważyć użycie biblioteki Room, która zapewnia warstwę abstrakcji nad SQLite, aby umożliwić bardziej niezawodny dostęp do bazy danych przy jednoczesnym wykorzystaniu pełnej mocy SQLite.

Konfigurowanie zależności

Aby skonfigurować SQLite w projekcie KMP, dodaj zależności artefaktów w pliku build.gradle.kts modułu:

[versions]
sqlite = "2.6.2"

[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" }

Interfejsy API sterownika SQLite

Biblioteka androidx.sqlite grupuje interfejsy API niskiego poziomu do komunikacji z biblioteką SQLite, która jest dołączona do biblioteki w przypadku używania androidx.sqlite:sqlite-bundled lub na platformie hosta, np. Androida lub iOS, w przypadku używania androidx.sqlite:sqlite-framework. Interfejsy API ściśle odpowiadają podstawowym funkcjom interfejsu SQLite C API.

Dostępne są 3 główne interfejsy:

  • SQLiteDriver – punkt wejścia do korzystania z SQLite, który odpowiada za otwieranie połączeń z bazą danych.
  • SQLiteConnection – reprezentacja obiektu sqlite3.
  • SQLiteStatement – reprezentacja obiektu sqlite3_stmt.

Ten przykład pokazuje podstawowe interfejsy API:

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()
}

Podobnie jak w przypadku interfejsów SQLite C API typowe użycie polega na:

  • otwieraniu połączenia z bazą danych za pomocą utworzonej implementacji SQLiteDriver.
  • przygotowywaniu instrukcji SQL za pomocą SQLiteConnection.prepare()
  • wykonywaniu SQLiteStatement w ten sposób:
    1. Opcjonalnie powiąż argumenty za pomocą funkcji bind*().
    2. Iteruj po zbiorze wyników za pomocą funkcji step().
    3. Odczytuj kolumny ze zbioru wyników za pomocą funkcji get*().

Implementacje sterowników

W tabeli poniżej znajdziesz podsumowanie dostępnych implementacji sterowników:

Nazwa zajęć

Artefakt

Obsługiwane platformy

AndroidSQLiteDriver androidx.sqlite:sqlite-framework

Android

NativeSQLiteDriver androidx.sqlite:sqlite-framework

iOS, Mac i Linux

BundledSQLiteDriver androidx.sqlite:sqlite-bundled

Android, iOS, Mac, Linux i JVM (komputer)

Zalecana implementacja to BundledSQLiteDriver dostępna w androidx.sqlite:sqlite-bundled. Zawiera ona bibliotekę SQLite skompilowaną ze źródła, dzięki czemu zapewnia najbardziej aktualną wersję i spójność na wszystkich obsługiwanych platformach KMP.

Sterownik SQLite i Room

Interfejsy API sterowników są przydatne do interakcji niskiego poziomu z bazą danych SQLite. Jeśli potrzebujesz biblioteki z wieloma funkcjami, która zapewnia bardziej niezawodny dostęp do SQLite, zalecamy użycie Room.

A RoomDatabase korzysta z SQLiteDriver do wykonywania operacji na bazie danych i implementację należy skonfigurować za pomocą RoomDatabase.Builder.setDriver(). Room udostępnia RoomDatabase.useReaderConnection i RoomDatabase.useWriterConnection do bardziej bezpośredniego dostępu do zarządzanych połączeń z bazą danych.

Migracja do Kotlin Multiplatform

Każde użycie komponentów interfejsu Support SQLite API niskiego poziomu (np. interfejsu SupportSQLiteDatabase) należy przenieść na równoważne komponenty sterownika SQLite.

Kotlin Multiplatform

Wykonaj transakcję za pomocą SQLiteConnection niskiego poziomu

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

Wykonaj zapytanie bez wyniku

val connection: SQLiteConnection = ...
connection.execSQL("ALTER TABLE ...")

Wykonaj zapytanie z wynikiem, ale bez argumentów

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

Wykonaj zapytanie z wynikiem i argumentami

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

Tylko Android

Wykonaj transakcję za pomocą SupportSQLiteDatabase

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

Wykonaj zapytanie bez wyniku

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

Wykonaj zapytanie z wynikiem, ale bez argumentów

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

Wykonaj zapytanie z wynikiem i argumentami

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