Flutter plugin that provides access to SQLite databases for Android, iOS, macOS, Linux, Windows and Web platforms.
- Android - Native library integration
- iOS - xcframework with optimized performance
- macOS - xcframework for both development and production
- Linux - Native shared libraries
- Windows - DLL integration
- Web - WASM SQLite via dedicated Web Worker with OPFS persistence
openDb()automatically creates a C-level connection pool with 1 writer + 4 readers (configurable)- WAL mode enables concurrent reads and writes
- Pool is fully transparent -- no API changes needed
- Reads automatically use pool readers; writes use the dedicated writer
- Falls back to single connection if pool creation fails or
readerPoolSize = 0 - Native pool has mutex-protected reader acquire/release for thread safety
- Writer lock: serializes all write operations (
executeSql, transactions) on both web and native - Independent readers: each
executeReadercall returns aDbasSqliteReaderwith its own pool connection -- multiple readers can be active simultaneously - Pool readers are acquired non-blocking; if all are busy, the reader falls back to the writer connection
- Transactions hold the writer lock for their full duration
- Reads within a transaction use the writer connection to see uncommitted data
- Readers must be closed explicitly via
reader.close()(or auto-closed whenreadRow()returnsfalse) before the connection can be reused closeDb()automatically closes all active readers, then releases all locks and unblocks any pending operations -- no risk of use-after-free on lingering readers
- All heavy FFI operations (
executeSql,prepareQuery,readRow,openDb,closeDb) run on a dedicated background isolate - Column data is pre-fetched into a Dart-side cache after each
readRow--getColumn*calls are synchronous reads from Dart memory, not FFI round-trips - Bind operations remain synchronous on the main isolate for performance
attachStreamDb(stream): Streams a database to OPFS chunk by chunk viaattachStreamBegin/attachStreamChunk/attachStreamEndwith ACK-based backpressure. The complete file is never buffered in Dart memory -- critical for large databases (500 MB+)getContent(): Streams the database from OPFS chunk by chunk. Supports both Transferable Streams (Chrome/Firefox) and chunked postMessage fallback (Safari)streamCopyDb(destDbName): Copies between OPFS files chunk by chunk
-
Lifecycle
getInstance(dbName:)- Get singleton instance for a databaseopenDb({readerPoolSize})- Open database with connection poolcloseDb()- Close database connection (automatically closes all active readers)isOpened()- Check connection statusgetAppDatabasePath()- Get platform-specific database pathdatabaseExists()- Check if the database file existsdropDb()- Delete the database file (including WAL and SHM)
-
Database Content
attachDb(bytes)- Attach a database from raw bytesattachStreamDb(stream)- Attach a database from a byte streamstreamCopyDb(destDbName)- Stream-copy database to a new namegetContent()- Get the raw bytes of the database file
-
SQL Execution
executeSql(sql, {params, nameParams})- Execute DDL/DML statements with optional positional or named parametersexecuteReader(sql, {params, nameParams})- Prepare a SELECT query, returns an independentDbasSqliteReader
-
Transactions
beginTransaction()- Begin a new transaction (idempotent)commit()- Commit the current transaction (idempotent)rollback()- Rollback the current transaction (idempotent)transaction(action)- Execute an action within a transaction with automatic commit/rollbackisInTransaction- Check if a transaction is currently active
readRow()- Advance to next row (trueif available,falseand auto-closes when done)close()- Manually close the reader and release its connection- Column Access (with nullable variants)
getColumnText(index)/getColumnNullableText(index)- Get string valuegetColumnInt(index)/getColumnNullableInt(index)- Get integer valuegetColumnBool(index)/getColumnNullableBool(index)- Get boolean valuegetColumnDouble(index)/getColumnNullableDouble(index)- Get double valuegetColumnDecimal(index)/getColumnNullableDecimal(index)- Get decimal valuegetColumnDateTime(index)/getColumnNullableDateTime(index)- Get DateTime valuegetColumnTime(index)/getColumnNullableTime(index)- Get Duration valuegetColumnEnum<T>(index, values)/getColumnNullableEnum<T>(index, values)- Get enum valuegetColumnBlob(index)/getColumnNullableBlob(index)- Get binary datagetColumnValue(index)- Get typed value based on SQLite column typeisColumnNull(index)- Check if column is NULLgetColumnType(index)- Get column data typegetColumnName(index)- Get column namegetColumnCount()- Get number of columns
getLastInsertedId()- Get last inserted row ID
Add to your pubspec.yaml:
dependencies:
dbas_sqlite: ^2.4.1Or install with the Dart CLI:
flutter pub add dbas_sqliteNo additional setup required. Native libraries are automatically bundled.
The WASM module and Web Worker are bundled as Flutter assets and loaded automatically by the plugin. No manual file copying or <script> tags needed.
The worker runs inside a dedicated Web Worker with OPFS persistence. Requires a modern browser with OPFS support (Chrome 86+, Firefox 111+, Safari 15.2+) served over HTTPS or localhost.
import 'package:dbas_sqlite/dbas_sqlite.dart';
// Get database instance
final db = await DbasSqlite.getInstance(dbName: 'myapp.db');
// Open database (creates pool with WAL mode)
await db.openDb();
// Create table
final createStmt = await db.prepareQuery('''
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE,
age INTEGER
)
''');
try {
await createStmt.executeSql();
} finally {
await createStmt.close();
}
// Insert with positional parameters
final insertStmt = await db.prepareQuery(
'INSERT INTO users (name, email, age) VALUES (?, ?, ?)',
);
try {
await insertStmt.executeSql(params: ['John Doe', 'john@example.com', 30]);
print('Inserted user with id: ${insertStmt.getLastInsertedId()}');
} finally {
await insertStmt.close();
}
// Query data
final selectStmt = await db.prepareQuery('SELECT * FROM users WHERE age > ?');
try {
final reader = await selectStmt.executeReader(params: [25]);
try {
while (await reader.readRow()) {
final name = reader.getColumnText(0);
final email = reader.getColumnNullableText(1);
final age = reader.getColumnInt(2);
print('$name ($email) - age: $age');
}
} finally {
await reader.close();
}
} finally {
await selectStmt.close();
}
await db.closeDb();A prepared statement can be executed many times with different params — the bind buffer is replayed against a fresh native handle on each call:
final stmt = await db.prepareQuery('INSERT INTO users (name) VALUES (?)');
try {
for (final name in ['Alice', 'Bob', 'Carol']) {
await stmt.executeSql(params: [name]);
}
} finally {
await stmt.close();
}final stmt = await db.prepareQuery(
'INSERT INTO users (name, email, age) VALUES (:name, :email, :age)',
);
try {
await stmt.executeSql(nameParams: {
'name': 'Jane Smith',
'email': 'jane@example.com',
'age': 28,
});
} finally {
await stmt.close();
}
// By default, extra named parameters not present in the SQL are silently
// ignored (C#/SQLite behavior). To throw instead:
db.throwOnMissingNamedParams = true;import 'package:decimal/decimal.dart';
// Supports Decimal, bool, DateTime, Duration, Enum and Blob
final insStmt = await db.prepareQuery(
'INSERT INTO products (name, price, active) VALUES (?, ?, ?)',
);
try {
await insStmt.executeSql(
params: ['Widget', Decimal.parse('19.99'), true],
);
} finally {
await insStmt.close();
}
final selStmt = await db.prepareQuery('SELECT name, price, active FROM products');
try {
final reader = await selStmt.executeReader();
try {
while (await reader.readRow()) {
final name = reader.getColumnText(0);
final price = reader.getColumnDecimal(1);
final active = reader.getColumnBool(2);
print('$name - \$$price (active: $active)');
}
} finally {
await reader.close();
}
} finally {
await selStmt.close();
}// Automatic commit/rollback
await db.transaction((tx) async {
final stmt = await tx.prepareQuery('INSERT INTO users (name) VALUES (?)');
try {
await stmt.executeSql(params: ['Alice']);
await stmt.executeSql(params: ['Bob']);
} finally {
await stmt.close();
}
});
// Manual control
await db.beginTransaction();
try {
final stmt = await db.prepareQuery('INSERT INTO users (name) VALUES (?)');
try {
await stmt.executeSql(params: ['Alice']);
} finally {
await stmt.close();
}
await db.commit();
} catch (_) {
await db.rollback();
rethrow;
}// Default: 4 readers (WAL mode)
await db.openDb();
// Custom pool size
await db.openDb(readerPoolSize: 8);
// Single connection (no pool)
await db.openDb(readerPoolSize: 0);// Copy database to a backup
await db.streamCopyDb('myapp_backup.db');| Platform | Minimum Version |
|---|---|
| Android | API 35 |
| iOS | 16.0 |
| macOS | 13.0 (Ventura) |
| Linux | x86_64 |
| Windows | x86_64 |
| Web | Modern browsers with OPFS support |
- iOS/macOS: Uses xcframework for optimal performance
- Android: Native library automatically included (NDK r29)
- Windows/Linux: Dynamic libraries bundled with app
- Web: WASM module runs in a dedicated Web Worker with OPFS persistence. All heavy operations are serialized through the worker — column data is cached in Dart memory after each
readRowfor synchronous access
This project is licensed under the terms specified in the LICENSE file.