Konfigurowanie SQLite na potrzeby KMP

Biblioteka androidx.sqlite zawiera abstrakcyjne interfejsy wraz z podstawowymi implementacjami, 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.0"

[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

Biblioteki androidx.sqlite oferują interfejsy API niskiego poziomu do komunikacji z biblioteką SQLite, która jest dołączana do biblioteki podczas korzystania z androidx.sqlite:sqlite-bundled lub na platformie hosta, np. Androida lub iOS, podczas korzystania z androidx.sqlite:sqlite-framework. Interfejsy API są ściśle zgodne z podstawowymi funkcjami interfejsu SQLite C API.

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

  • SQLiteDriver – to punkt wejścia do korzystania z bazy danych SQLite. Odpowiada za otwieranie połączeń z bazą danych.
  • SQLiteConnection – reprezentacja obiektu sqlite3.
  • SQLiteStatement – reprezentacja obiektu sqlite3_stmt.

Poniższy przykład przedstawia 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 API SQLite C, typowe zastosowanie polega na:

  • Otwórz połączenie z bazą danych za pomocą utworzonego wystąpienia SQLiteDriver implementacji.
  • Przygotuj instrukcję SQL za pomocą SQLiteConnection.prepare()
  • Wykonaj SQLiteStatement w ten sposób:
    1. Opcjonalnie możesz powiązać argumenty za pomocą funkcji bind*().
    2. Iteruj po zbiorze wyników za pomocą funkcji step().
    3. Odczytuj kolumny z zestawu 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 (komputery)

Zalecana implementacja to BundledSQLiteDriver dostępna w androidx.sqlite:sqlite-bundled. Zawiera bibliotekę SQLite skompilowaną z kodu źródłowego, która oferuje najnowszą wersję i spójność na wszystkich obsługiwanych platformach KMP.

Sterownik SQLite i Room

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

RoomDatabase korzysta z SQLiteDriver do wykonywania operacji na bazie danych, a implementację należy skonfigurować za pomocą RoomDatabase.Builder.setDriver(). Room udostępnia RoomDatabase.useReaderConnectionRoomDatabase.useWriterConnection, aby zapewnić bardziej bezpośredni dostęp do zarządzanych połączeń z bazą danych.

Migracja do Kotlin Multiplatform

Wszelkie użycie komponentów interfejsu Support SQLite API niskiego poziomu (np. interfejsu SupportSQLiteDatabase) należy przenieść do odpowiednich komponentów sterownika SQLite.

Kotlin Multiplatform

Przeprowadzanie transakcji za pomocą interfejsu 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 wyników

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

Przeprowadź transakcję za pomocą SupportSQLiteDatabase

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

Wykonaj zapytanie bez wyników

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