CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-app-cash-sqldelight--android-driver

Driver to support SQLDelight generated code on Android

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

index.mddocs/

SQLDelight Android Driver

SQLDelight Android Driver provides a SQLite database driver implementation specifically designed for Android applications using SQLDelight. It serves as a bridge between SQLDelight's generated typesafe Kotlin APIs and Android's SQLite database system through the AndroidX SQLite library, with features like statement caching, transaction support, and reactive query listeners.

Package Information

  • Package Name: app.cash.sqldelight:android-driver
  • Package Type: Maven (Android Library)
  • Language: Kotlin
  • Installation: Add to build.gradle.kts:
    dependencies {
      implementation("app.cash.sqldelight:android-driver:2.1.0")
      api("androidx.sqlite:sqlite:2.3.1")
    }

Core Imports

import app.cash.sqldelight.driver.android.AndroidSqliteDriver
import app.cash.sqldelight.db.SqlSchema
import app.cash.sqldelight.db.QueryResult
import android.content.Context

Basic Usage

import app.cash.sqldelight.driver.android.AndroidSqliteDriver
import app.cash.sqldelight.db.SqlSchema
import android.content.Context

// Create driver with schema and context
val driver = AndroidSqliteDriver(
  schema = MyDatabase.Schema,
  context = applicationContext,
  name = "my_database.db"
)

// Use with SQLDelight generated database
val database = MyDatabase(driver)

// Perform database operations
val users = database.userQueries.selectAll().executeAsList()

// Close when done
driver.close()

Architecture

The Android Driver is built around several key components:

  • SqlDriver Interface: Implementation of SQLDelight's core driver interface
  • Statement Caching: LRU cache for prepared statements with configurable size
  • Transaction Management: Nested transaction support with thread-local storage
  • Query Listeners: Reactive change notifications for query result updates
  • AndroidX Integration: Native integration with AndroidX SQLite library
  • Cursor Management: Configurable cursor window size for memory optimization

Capabilities

Driver Creation

Create AndroidSqliteDriver instances for different use cases.

/**
 * Create driver with schema, context, and optional configuration
 */
class AndroidSqliteDriver(
  schema: SqlSchema<QueryResult.Value<Unit>>,
  context: Context,
  name: String? = null,
  factory: SupportSQLiteOpenHelper.Factory = FrameworkSQLiteOpenHelperFactory(),
  callback: SupportSQLiteOpenHelper.Callback = AndroidSqliteDriver.Callback(schema),
  cacheSize: Int = 20,
  useNoBackupDirectory: Boolean = false,
  windowSizeBytes: Long? = null
)

/**
 * Create driver from existing SupportSQLiteOpenHelper
 */
constructor(openHelper: SupportSQLiteOpenHelper)

/**
 * Create driver from existing SupportSQLiteDatabase
 */
constructor(
  database: SupportSQLiteDatabase,
  cacheSize: Int = 20,
  windowSizeBytes: Long? = null
)

Parameters:

  • schema: SQLDelight schema containing DDL and migrations
  • context: Android Context for database file access
  • name: Database file name (null for in-memory database)
  • factory: Factory for creating SupportSQLiteOpenHelper (defaults to FrameworkSQLiteOpenHelperFactory)
  • callback: Database lifecycle callback handler (defaults to AndroidSqliteDriver.Callback)
  • cacheSize: Number of prepared statements to cache (defaults to 20)
  • useNoBackupDirectory: Whether to prevent database backup (defaults to false)
  • windowSizeBytes: Cursor window size in bytes for Android 28+ (defaults to null)

SQL Execution

Execute SQL statements and queries with parameter binding.

/**
 * Execute SQL statement (INSERT, UPDATE, DELETE)
 * @param identifier Optional cache key for statement caching
 * @param sql SQL statement string
 * @param parameters Number of bindable parameters
 * @param binders Function to bind parameters to statement
 * @return Number of affected rows
 */
fun execute(
  identifier: Int?,
  sql: String,
  parameters: Int,
  binders: (SqlPreparedStatement.() -> Unit)? = null
): QueryResult<Long>

/**
 * Execute SQL query (SELECT) and map results
 * @param identifier Optional cache key for statement caching
 * @param sql SQL query string
 * @param mapper Function to map cursor results to return type
 * @param parameters Number of bindable parameters
 * @param binders Function to bind parameters to statement
 * @return Mapped query results
 */
fun <R> executeQuery(
  identifier: Int?,
  sql: String,
  mapper: (SqlCursor) -> QueryResult<R>,
  parameters: Int,
  binders: (SqlPreparedStatement.() -> Unit)? = null
): QueryResult<R>

Transaction Management

Manage database transactions with proper nesting support.

/**
 * Start a new database transaction
 * @return Transaction instance for controlling transaction lifecycle
 */
fun newTransaction(): QueryResult<Transacter.Transaction>

/**
 * Get the currently active transaction
 * @return Current transaction or null if none active
 */
fun currentTransaction(): Transacter.Transaction?

Transaction Class:

inner class Transaction(
  override val enclosingTransaction: Transacter.Transaction?
) : Transacter.Transaction() {
  /**
   * End the transaction
   * @param successful Whether to commit (true) or rollback (false)
   * @return QueryResult<Unit> indicating completion
   */
  override fun endTransaction(successful: Boolean): QueryResult<Unit>
}

Query Listeners

Register listeners for reactive query result change notifications.

/**
 * Add listener for query result changes
 * @param queryKeys Variable number of query keys to listen for
 * @param listener Listener to be notified of changes
 */
fun addListener(vararg queryKeys: String, listener: Query.Listener)

/**
 * Remove previously registered listener
 * @param queryKeys Variable number of query keys to stop listening for
 * @param listener Listener to remove
 */
fun removeListener(vararg queryKeys: String, listener: Query.Listener)

/**
 * Notify all registered listeners of query changes
 * @param queryKeys Variable number of query keys that changed
 */
fun notifyListeners(vararg queryKeys: String)

Resource Management

Properly close and cleanup database resources.

/**
 * Close database connection and cleanup resources
 * Evicts all cached statements and closes underlying database
 */
fun close()

Database Callbacks

Handle database lifecycle events for schema creation and migration.

/**
 * Database callback handler for schema creation and migration
 */
open class Callback(
  private val schema: SqlSchema<QueryResult.Value<Unit>>,
  private vararg val callbacks: AfterVersion
) : SupportSQLiteOpenHelper.Callback(schema.version.toInt()) {
  
  /**
   * Called when database is created for the first time
   * @param db Database instance being created
   */
  override fun onCreate(db: SupportSQLiteDatabase)
  
  /**
   * Called when database needs to be upgraded
   * @param db Database instance being upgraded
   * @param oldVersion Previous database version
   * @param newVersion Target database version
   */
  override fun onUpgrade(
    db: SupportSQLiteDatabase,
    oldVersion: Int,
    newVersion: Int
  )
}

Types

/**
 * Interface for SQL cursor operations
 */
interface SqlCursor {
  fun next(): QueryResult<Boolean>
  fun getString(index: Int): String?
  fun getLong(index: Int): Long?
  fun getBytes(index: Int): ByteArray?
  fun getDouble(index: Int): Double?
  fun getBoolean(index: Int): Boolean?
}

/**
 * Interface for prepared statement parameter binding
 */
interface SqlPreparedStatement {
  fun bindBytes(index: Int, bytes: ByteArray?)
  fun bindLong(index: Int, long: Long?)
  fun bindDouble(index: Int, double: Double?)
  fun bindString(index: Int, string: String?)
  fun bindBoolean(index: Int, boolean: Boolean?)
}

/**
 * Schema definition interface containing DDL and migrations
 */
interface SqlSchema<T> {
  val version: Long
  fun create(driver: SqlDriver): T
  fun migrate(
    driver: SqlDriver,
    oldVersion: Long,
    newVersion: Long,
    vararg callbacks: AfterVersion
  ): T
}

/**
 * Result wrapper type for database operations
 */
sealed interface QueryResult<out T> {
  val value: T
  
  object Unit : QueryResult<kotlin.Unit> {
    override val value: kotlin.Unit = kotlin.Unit
  }
  
  data class Value<T>(override val value: T) : QueryResult<T>
}

/**
 * Migration callback executed after version upgrade
 */
class AfterVersion(
  val afterVersion: Long,
  val block: (SqlDriver) -> kotlin.Unit
)

/**
 * Query change listener interface
 */
interface Query.Listener {
  fun queryResultsChanged()
}

/**
 * Transaction interface for database transaction control
 */
abstract class Transacter.Transaction {
  abstract val enclosingTransaction: Transacter.Transaction?
  abstract fun endTransaction(successful: Boolean): QueryResult<kotlin.Unit>
}

Error Handling

The Android Driver handles common database errors through the QueryResult type system and standard SQLite exceptions:

  • SQLiteException: Thrown for SQL syntax errors, constraint violations, and database corruption
  • IllegalStateException: Thrown when attempting operations on closed driver or invalid transaction state
  • IllegalArgumentException: Thrown for invalid constructor parameters or method arguments

Common error scenarios:

  • Database file corruption or inaccessible storage
  • SQL constraint violations (UNIQUE, FOREIGN KEY, CHECK)
  • Transaction deadlocks or timeout issues
  • Invalid SQL syntax in statements

Thread Safety

The AndroidSqliteDriver is designed for multi-threaded access with the following guarantees:

  • Statement caching: Thread-safe LRU cache with proper synchronization
  • Transaction management: Thread-local transaction storage prevents cross-thread interference
  • Query listeners: Synchronized listener registration and notification
  • Database operations: All operations are properly synchronized through the underlying AndroidX SQLite library

Best practices:

  • Use a single driver instance per database across your application
  • Transactions are bound to the calling thread and cannot be shared
  • Query listeners are called on the thread that triggered the database change

docs

index.md

tile.json