Driver to support SQLDelight generated code on Android
npx @tessl/cli install tessl/maven-app-cash-sqldelight--android-driver@2.1.00
# SQLDelight Android Driver
1
2
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.
3
4
## Package Information
5
6
- **Package Name**: app.cash.sqldelight:android-driver
7
- **Package Type**: Maven (Android Library)
8
- **Language**: Kotlin
9
- **Installation**: Add to `build.gradle.kts`:
10
```kotlin
11
dependencies {
12
implementation("app.cash.sqldelight:android-driver:2.1.0")
13
api("androidx.sqlite:sqlite:2.3.1")
14
}
15
```
16
17
## Core Imports
18
19
```kotlin
20
import app.cash.sqldelight.driver.android.AndroidSqliteDriver
21
import app.cash.sqldelight.db.SqlSchema
22
import app.cash.sqldelight.db.QueryResult
23
import android.content.Context
24
```
25
26
## Basic Usage
27
28
```kotlin
29
import app.cash.sqldelight.driver.android.AndroidSqliteDriver
30
import app.cash.sqldelight.db.SqlSchema
31
import android.content.Context
32
33
// Create driver with schema and context
34
val driver = AndroidSqliteDriver(
35
schema = MyDatabase.Schema,
36
context = applicationContext,
37
name = "my_database.db"
38
)
39
40
// Use with SQLDelight generated database
41
val database = MyDatabase(driver)
42
43
// Perform database operations
44
val users = database.userQueries.selectAll().executeAsList()
45
46
// Close when done
47
driver.close()
48
```
49
50
## Architecture
51
52
The Android Driver is built around several key components:
53
54
- **SqlDriver Interface**: Implementation of SQLDelight's core driver interface
55
- **Statement Caching**: LRU cache for prepared statements with configurable size
56
- **Transaction Management**: Nested transaction support with thread-local storage
57
- **Query Listeners**: Reactive change notifications for query result updates
58
- **AndroidX Integration**: Native integration with AndroidX SQLite library
59
- **Cursor Management**: Configurable cursor window size for memory optimization
60
61
## Capabilities
62
63
### Driver Creation
64
65
Create AndroidSqliteDriver instances for different use cases.
66
67
```kotlin { .api }
68
/**
69
* Create driver with schema, context, and optional configuration
70
*/
71
class AndroidSqliteDriver(
72
schema: SqlSchema<QueryResult.Value<Unit>>,
73
context: Context,
74
name: String? = null,
75
factory: SupportSQLiteOpenHelper.Factory = FrameworkSQLiteOpenHelperFactory(),
76
callback: SupportSQLiteOpenHelper.Callback = AndroidSqliteDriver.Callback(schema),
77
cacheSize: Int = 20,
78
useNoBackupDirectory: Boolean = false,
79
windowSizeBytes: Long? = null
80
)
81
82
/**
83
* Create driver from existing SupportSQLiteOpenHelper
84
*/
85
constructor(openHelper: SupportSQLiteOpenHelper)
86
87
/**
88
* Create driver from existing SupportSQLiteDatabase
89
*/
90
constructor(
91
database: SupportSQLiteDatabase,
92
cacheSize: Int = 20,
93
windowSizeBytes: Long? = null
94
)
95
```
96
97
**Parameters:**
98
- `schema`: SQLDelight schema containing DDL and migrations
99
- `context`: Android Context for database file access
100
- `name`: Database file name (null for in-memory database)
101
- `factory`: Factory for creating SupportSQLiteOpenHelper (defaults to FrameworkSQLiteOpenHelperFactory)
102
- `callback`: Database lifecycle callback handler (defaults to AndroidSqliteDriver.Callback)
103
- `cacheSize`: Number of prepared statements to cache (defaults to 20)
104
- `useNoBackupDirectory`: Whether to prevent database backup (defaults to false)
105
- `windowSizeBytes`: Cursor window size in bytes for Android 28+ (defaults to null)
106
107
### SQL Execution
108
109
Execute SQL statements and queries with parameter binding.
110
111
```kotlin { .api }
112
/**
113
* Execute SQL statement (INSERT, UPDATE, DELETE)
114
* @param identifier Optional cache key for statement caching
115
* @param sql SQL statement string
116
* @param parameters Number of bindable parameters
117
* @param binders Function to bind parameters to statement
118
* @return Number of affected rows
119
*/
120
fun execute(
121
identifier: Int?,
122
sql: String,
123
parameters: Int,
124
binders: (SqlPreparedStatement.() -> Unit)? = null
125
): QueryResult<Long>
126
127
/**
128
* Execute SQL query (SELECT) and map results
129
* @param identifier Optional cache key for statement caching
130
* @param sql SQL query string
131
* @param mapper Function to map cursor results to return type
132
* @param parameters Number of bindable parameters
133
* @param binders Function to bind parameters to statement
134
* @return Mapped query results
135
*/
136
fun <R> executeQuery(
137
identifier: Int?,
138
sql: String,
139
mapper: (SqlCursor) -> QueryResult<R>,
140
parameters: Int,
141
binders: (SqlPreparedStatement.() -> Unit)? = null
142
): QueryResult<R>
143
```
144
145
### Transaction Management
146
147
Manage database transactions with proper nesting support.
148
149
```kotlin { .api }
150
/**
151
* Start a new database transaction
152
* @return Transaction instance for controlling transaction lifecycle
153
*/
154
fun newTransaction(): QueryResult<Transacter.Transaction>
155
156
/**
157
* Get the currently active transaction
158
* @return Current transaction or null if none active
159
*/
160
fun currentTransaction(): Transacter.Transaction?
161
```
162
163
**Transaction Class:**
164
```kotlin { .api }
165
inner class Transaction(
166
override val enclosingTransaction: Transacter.Transaction?
167
) : Transacter.Transaction() {
168
/**
169
* End the transaction
170
* @param successful Whether to commit (true) or rollback (false)
171
* @return QueryResult<Unit> indicating completion
172
*/
173
override fun endTransaction(successful: Boolean): QueryResult<Unit>
174
}
175
```
176
177
### Query Listeners
178
179
Register listeners for reactive query result change notifications.
180
181
```kotlin { .api }
182
/**
183
* Add listener for query result changes
184
* @param queryKeys Variable number of query keys to listen for
185
* @param listener Listener to be notified of changes
186
*/
187
fun addListener(vararg queryKeys: String, listener: Query.Listener)
188
189
/**
190
* Remove previously registered listener
191
* @param queryKeys Variable number of query keys to stop listening for
192
* @param listener Listener to remove
193
*/
194
fun removeListener(vararg queryKeys: String, listener: Query.Listener)
195
196
/**
197
* Notify all registered listeners of query changes
198
* @param queryKeys Variable number of query keys that changed
199
*/
200
fun notifyListeners(vararg queryKeys: String)
201
```
202
203
### Resource Management
204
205
Properly close and cleanup database resources.
206
207
```kotlin { .api }
208
/**
209
* Close database connection and cleanup resources
210
* Evicts all cached statements and closes underlying database
211
*/
212
fun close()
213
```
214
215
### Database Callbacks
216
217
Handle database lifecycle events for schema creation and migration.
218
219
```kotlin { .api }
220
/**
221
* Database callback handler for schema creation and migration
222
*/
223
open class Callback(
224
private val schema: SqlSchema<QueryResult.Value<Unit>>,
225
private vararg val callbacks: AfterVersion
226
) : SupportSQLiteOpenHelper.Callback(schema.version.toInt()) {
227
228
/**
229
* Called when database is created for the first time
230
* @param db Database instance being created
231
*/
232
override fun onCreate(db: SupportSQLiteDatabase)
233
234
/**
235
* Called when database needs to be upgraded
236
* @param db Database instance being upgraded
237
* @param oldVersion Previous database version
238
* @param newVersion Target database version
239
*/
240
override fun onUpgrade(
241
db: SupportSQLiteDatabase,
242
oldVersion: Int,
243
newVersion: Int
244
)
245
}
246
```
247
248
## Types
249
250
```kotlin { .api }
251
/**
252
* Interface for SQL cursor operations
253
*/
254
interface SqlCursor {
255
fun next(): QueryResult<Boolean>
256
fun getString(index: Int): String?
257
fun getLong(index: Int): Long?
258
fun getBytes(index: Int): ByteArray?
259
fun getDouble(index: Int): Double?
260
fun getBoolean(index: Int): Boolean?
261
}
262
263
/**
264
* Interface for prepared statement parameter binding
265
*/
266
interface SqlPreparedStatement {
267
fun bindBytes(index: Int, bytes: ByteArray?)
268
fun bindLong(index: Int, long: Long?)
269
fun bindDouble(index: Int, double: Double?)
270
fun bindString(index: Int, string: String?)
271
fun bindBoolean(index: Int, boolean: Boolean?)
272
}
273
274
/**
275
* Schema definition interface containing DDL and migrations
276
*/
277
interface SqlSchema<T> {
278
val version: Long
279
fun create(driver: SqlDriver): T
280
fun migrate(
281
driver: SqlDriver,
282
oldVersion: Long,
283
newVersion: Long,
284
vararg callbacks: AfterVersion
285
): T
286
}
287
288
/**
289
* Result wrapper type for database operations
290
*/
291
sealed interface QueryResult<out T> {
292
val value: T
293
294
object Unit : QueryResult<kotlin.Unit> {
295
override val value: kotlin.Unit = kotlin.Unit
296
}
297
298
data class Value<T>(override val value: T) : QueryResult<T>
299
}
300
301
/**
302
* Migration callback executed after version upgrade
303
*/
304
class AfterVersion(
305
val afterVersion: Long,
306
val block: (SqlDriver) -> kotlin.Unit
307
)
308
309
/**
310
* Query change listener interface
311
*/
312
interface Query.Listener {
313
fun queryResultsChanged()
314
}
315
316
/**
317
* Transaction interface for database transaction control
318
*/
319
abstract class Transacter.Transaction {
320
abstract val enclosingTransaction: Transacter.Transaction?
321
abstract fun endTransaction(successful: Boolean): QueryResult<kotlin.Unit>
322
}
323
```
324
325
## Error Handling
326
327
The Android Driver handles common database errors through the QueryResult type system and standard SQLite exceptions:
328
329
- **SQLiteException**: Thrown for SQL syntax errors, constraint violations, and database corruption
330
- **IllegalStateException**: Thrown when attempting operations on closed driver or invalid transaction state
331
- **IllegalArgumentException**: Thrown for invalid constructor parameters or method arguments
332
333
Common error scenarios:
334
- Database file corruption or inaccessible storage
335
- SQL constraint violations (UNIQUE, FOREIGN KEY, CHECK)
336
- Transaction deadlocks or timeout issues
337
- Invalid SQL syntax in statements
338
339
## Thread Safety
340
341
The AndroidSqliteDriver is designed for multi-threaded access with the following guarantees:
342
343
- **Statement caching**: Thread-safe LRU cache with proper synchronization
344
- **Transaction management**: Thread-local transaction storage prevents cross-thread interference
345
- **Query listeners**: Synchronized listener registration and notification
346
- **Database operations**: All operations are properly synchronized through the underlying AndroidX SQLite library
347
348
Best practices:
349
- Use a single driver instance per database across your application
350
- Transactions are bound to the calling thread and cannot be shared
351
- Query listeners are called on the thread that triggered the database change