Настройка SQLite для KMP

Библиотека androidx.sqlite содержит абстрактные интерфейсы, а также базовые реализации, которые можно использовать для создания собственных библиотек, обращающихся к SQLite. Возможно, вам также стоит рассмотреть библиотеку Room , которая предоставляет уровень абстракции над SQLite, обеспечивая более надежный доступ к базе данных и используя всю мощь SQLite.

Настройка зависимостей

Для настройки SQLite в вашем проекте KMP добавьте зависимости для артефактов в файл build.gradle.kts для вашего модуля:

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

API драйвера SQLite

Библиотечные группы androidx.sqlite предоставляют низкоуровневые API для взаимодействия с библиотекой SQLite, которая либо включена в саму библиотеку при использовании androidx.sqlite:sqlite-bundled , либо находится на хост-платформе, например, Android или iOS, при использовании androidx.sqlite:sqlite-framework . Эти API в значительной степени соответствуют основной функциональности SQLite C API.

Существует 3 основных интерфейса:

  • SQLiteDriver — это точка входа для использования SQLite, отвечающая за открытие соединений с базой данных.
  • SQLiteConnection — это представление объекта sqlite3 .
  • SQLiteStatement — это представление объекта sqlite3_stmt .

В следующем примере показаны основные 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()
}

Подобно C API SQLite, обычное использование заключается в следующем:

  • Откройте соединение с базой данных, используя созданный экземпляр SQLiteDriver .
  • Подготовьте SQL-запрос с помощью SQLiteConnection.prepare()
  • Выполните SQLiteStatement следующим образом:
    1. При желании можно привязать аргументы с помощью функций bind*() .
    2. Выполните итерацию по набору результатов, используя функцию step() .
    3. Считывайте столбцы из результирующего набора данных, используя функции get*() .

Реализации драйверов

В следующей таблице приведено краткое описание доступных реализаций драйверов:

Название класса

Артефакт

Поддерживаемые платформы

AndroidSQLiteDriver androidx.sqlite:sqlite-framework

Android

NativeSQLiteDriver androidx.sqlite:sqlite-framework

iOS, Mac и Linux

BundledSQLiteDriver androidx.sqlite:sqlite-bundled

Android, iOS, Mac, Linux и JVM (для настольных компьютеров)

Рекомендуемая реализация — BundledSQLiteDriver , доступный в androidx.sqlite:sqlite-bundled . Он включает библиотеку SQLite, скомпилированную из исходного кода, обеспечивая самую актуальную версию и согласованность на всех поддерживаемых платформах KMP.

Драйвер и комната SQLite

API драйвера полезны для низкоуровневого взаимодействия с базой данных SQLite. Для более надежного доступа к SQLite рекомендуется использовать библиотеку Room, обладающую богатым функционалом и обеспечивающую более широкий доступ.

RoomDatabase использует SQLiteDriver для выполнения операций с базой данных, и для его реализации требуется настройка с помощью RoomDatabase.Builder.setDriver() . Room предоставляет RoomDatabase.useReaderConnection и RoomDatabase.useWriterConnection для более прямого доступа к управляемым соединениям с базой данных.

Переход на Kotlin Multiplatform

Любое использование низкоуровневых компонентов API поддержки SQLite (таких как интерфейс SupportSQLiteDatabase ) необходимо перенести на эквивалентные компоненты драйвера SQLite.

Kotlin Multiplatform

Выполните транзакцию, используя низкоуровневое SQLiteConnection

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

Выполнен запрос, не давший результата.

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

Выполнить запрос с результатом, но без аргументов.

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

Выполните запрос, получив результат и аргументы.

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

Только для Android

Выполните транзакцию с использованием SupportSQLiteDatabase

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

Выполнен запрос, не давший результата.

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

Выполнить запрос с результатом, но без аргументов.

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

Выполните запрос, получив результат и аргументы.

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