or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

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

Driver to support SQLDelight generated code on Android

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
mavenpkg:maven/app.cash.sqldelight/android-driver@2.1.x

To install, run

npx @tessl/cli install tessl/maven-app-cash-sqldelight--android-driver@2.1.0

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