sqflite_common_ffi_async: sqflite API on top of sqlite_async
package:sqflite_common_ffi_async exposes the sqflite DatabaseFactory /
Database API backed by package:sqlite_async (a connection pool with one
writer and several readers) instead of the single background isolate of
sqflite_common_ffi. Use it on Linux, macOS, Windows (Dart VM or Flutter
desktop) when reads must not wait behind writes. It is experimental (1.0.x).
import 'package:sqflite_common_ffi/sqflite_ffi.dart' show sqfliteFfiInit;
import 'package:sqflite_common_ffi_async/sqflite_ffi_async.dart';
Future<void> main() async {
sqfliteFfiInit();
var db = await databaseFactoryFfiAsync.openDatabase('example.db');
await db.execute('CREATE TABLE IF NOT EXISTS Product (id INTEGER PRIMARY KEY, title TEXT)');
await db.insert('Product', {'title': 'Product 1'});
print(await db.query('Product'));
await db.close();
}
Guidelines
- Depend on both
sqflite_common_ffi_async and sqflite_common_ffi (the
async package delegates some operations to it). Import
package:sqflite_common_ffi_async/sqflite_ffi_async.dart; it re-exports
package:sqflite_common/sqflite.dart (Database, OpenDatabaseOptions,
inMemoryDatabasePath, global databaseFactory...). sqfliteFfiInit
comes from package:sqflite_common_ffi/sqflite_ffi.dart; call it once at
startup (Windows setup, no-op elsewhere).
- Public API:
databaseFactoryFfiAsync (tag ffi_async) and
databaseFactoryFfiAsyncTest (tag ffi_async_test, a second independent
factory instance for tests). Everything else is under src/.
- The
Database API (query, insert, transaction, batch,
OpenDatabaseOptions callbacks) is the standard sqflite one, documented by
the sqflite / sqflite_common skills. Code written against
DatabaseFactory works unchanged; only the factory differs.
- What
sqlite_async adds:
db.readTransaction((txn) async { ... }) runs on a read connection
concurrently with writes. Only reads are allowed inside: a write through
that txn throws a DatabaseException ("read transaction cannot be used
for write"). On other sqflite implementations readTransaction is not
supported, so keep it behind this factory.
db.transaction(...) is a sqlite_async write transaction. Several
transaction calls from different callers are queued by the pool, not by
sqflite, so reads issued outside a transaction are not blocked by a
running write transaction.
- Falls back to
databaseFactoryFfi (regular ffi, separate isolate):
openDatabase(inMemoryDatabasePath): in-memory databases come from
sqflite_common_ffi (mainly for tests).
openDatabase(path, options: OpenDatabaseOptions(readOnly: true)).
deleteDatabase(path) and getDatabasesPath() (default is
<cwd>/.dart_tool/sqflite_common_ffi/databases, relative paths resolve
there).
- The parent directory of a database file is created on open. Prefer absolute
paths in applications.
- Limitations (from the package README and source):
singleInstance is ignored: sqlite_async manages opening/closing.
- No logger support (
SqfliteLoggerDatabaseFactory wrappers are not
wired in).
queryCursor / rawQueryCursor load the whole result set, no paging.
- io only (
platforms: linux, macos, windows, android, ios); no web.
Calling on the web throws UnsupportedError.
- After
close(), any call fails with a database_closed error.
- For unit tests prefer
databaseFactoryFfi from sqflite_common_ffi (see
sqflite-common-ffi-testing) unless the test exercises readTransaction
or concurrency behavior; then use databaseFactoryFfiAsyncTest with a
file path and deleteDatabase in setUp.
Examples
Concurrent read during a long write transaction
import 'package:sqflite_common_ffi/sqflite_ffi.dart' show sqfliteFfiInit;
import 'package:sqflite_common_ffi_async/sqflite_ffi_async.dart';
Future<void> main() async {
sqfliteFfiInit();
var factory = databaseFactoryFfiAsync;
var db = await factory.openDatabase(
'concurrent.db',
options: OpenDatabaseOptions(
version: 1,
onCreate: (db, _) => db.execute(
'CREATE TABLE Item (id INTEGER PRIMARY KEY, name TEXT)',
),
),
);
var write = db.transaction((txn) async {
await txn.insert('Item', {'name': 'slow'});
await Future<void>.delayed(const Duration(milliseconds: 500));
await txn.insert('Item', {'name': 'write'});
});
// Runs on a reader connection while the write transaction is open.
var count = await db.readTransaction((txn) async {
var rows = await txn.rawQuery('SELECT COUNT(*) AS c FROM Item');
return rows.first['c'] as int;
});
await write;
print(count); // 0: the write transaction had not committed yet
await db.close();
}
Test with the dedicated test factory
@TestOn('vm')
library;
import 'package:sqflite_common_ffi/sqflite_ffi.dart' show sqfliteFfiInit;
import 'package:sqflite_common_ffi_async/sqflite_ffi_async.dart';
import 'package:test/test.dart';
void main() {
sqfliteFfiInit();
final factory = databaseFactoryFfiAsyncTest;
const path = 'ffi_async_test.db';
setUp(() => factory.deleteDatabase(path));
test('version and insert', () async {
var db = await factory.openDatabase(
path,
options: OpenDatabaseOptions(
version: 1,
onCreate: (db, _) => db.execute('CREATE TABLE Test (id INTEGER PRIMARY KEY)'),
),
);
expect(await db.getVersion(), 1);
expect(await db.insert('Test', {'id': 1}), 1);
await db.close();
});
}
Selecting the factory per platform
import 'dart:io';
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
import 'package:sqflite_common_ffi_async/sqflite_ffi_async.dart';
DatabaseFactory pickFactory() {
sqfliteFfiInit();
if (Platform.isLinux || Platform.isMacOS || Platform.isWindows) {
return databaseFactoryFfiAsync;
}
return databaseFactoryFfi;
}
Common mistakes
- Writing inside
readTransaction: throws. Use transaction for writes.
- Using
readTransaction with databaseFactoryFfi or the sqflite plugin:
not supported there.
- Expecting
singleInstance: true semantics (same Database object for the
same path): the async factory does not honor it.
- Forgetting
sqfliteFfiInit() on Windows.
- Using it on the web: use
sqflite_common_ffi_web instead.
- Depending only on
sqflite_common_ffi_async and importing
sqflite_common_ffi/sqflite_ffi.dart transitively; declare both.
1---2name: sqflite-common-ffi-async-factory3description: Use when using package:sqflite_common_ffi_async, the experimental sqflite DatabaseFactory built on sqlite_async (PowerSync) for desktop and the Dart VM: databaseFactoryFfiAsync, databaseFactoryFfiAsyncTest, readTransaction and concurrent reads, how it differs from sqflite_common_ffi (databaseFactoryFfi), what falls back to plain ffi (in-memory, read-only), and its limitations (no logger, singleInstance ignored, io only).4---56# sqflite_common_ffi_async: sqflite API on top of sqlite_async78`package:sqflite_common_ffi_async` exposes the sqflite `DatabaseFactory` /9`Database` API backed by `package:sqlite_async` (a connection pool with one10writer and several readers) instead of the single background isolate of11`sqflite_common_ffi`. Use it on Linux, macOS, Windows (Dart VM or Flutter12desktop) when reads must not wait behind writes. It is experimental (1.0.x).1314```dart15import 'package:sqflite_common_ffi/sqflite_ffi.dart' show sqfliteFfiInit;16import 'package:sqflite_common_ffi_async/sqflite_ffi_async.dart';1718Future<void> main() async {19 sqfliteFfiInit();20 var db = await databaseFactoryFfiAsync.openDatabase('example.db');21 await db.execute('CREATE TABLE IF NOT EXISTS Product (id INTEGER PRIMARY KEY, title TEXT)');22 await db.insert('Product', {'title': 'Product 1'});23 print(await db.query('Product'));24 await db.close();25}26```2728## Guidelines2930* Depend on both `sqflite_common_ffi_async` and `sqflite_common_ffi` (the31 async package delegates some operations to it). Import32 `package:sqflite_common_ffi_async/sqflite_ffi_async.dart`; it re-exports33 `package:sqflite_common/sqflite.dart` (`Database`, `OpenDatabaseOptions`,34 `inMemoryDatabasePath`, global `databaseFactory`...). `sqfliteFfiInit`35 comes from `package:sqflite_common_ffi/sqflite_ffi.dart`; call it once at36 startup (Windows setup, no-op elsewhere).37* Public API: `databaseFactoryFfiAsync` (tag `ffi_async`) and38 `databaseFactoryFfiAsyncTest` (tag `ffi_async_test`, a second independent39 factory instance for tests). Everything else is under `src/`.40* The `Database` API (`query`, `insert`, `transaction`, `batch`,41 `OpenDatabaseOptions` callbacks) is the standard sqflite one, documented by42 the `sqflite` / `sqflite_common` skills. Code written against43 `DatabaseFactory` works unchanged; only the factory differs.44* What `sqlite_async` adds:45 * `db.readTransaction((txn) async { ... })` runs on a read connection46 concurrently with writes. Only reads are allowed inside: a write through47 that `txn` throws a `DatabaseException` ("read transaction cannot be used48 for write"). On other sqflite implementations `readTransaction` is not49 supported, so keep it behind this factory.50 * `db.transaction(...)` is a `sqlite_async` write transaction. Several51 `transaction` calls from different callers are queued by the pool, not by52 sqflite, so reads issued outside a transaction are not blocked by a53 running write transaction.54* Falls back to `databaseFactoryFfi` (regular ffi, separate isolate):55 * `openDatabase(inMemoryDatabasePath)`: in-memory databases come from56 `sqflite_common_ffi` (mainly for tests).57 * `openDatabase(path, options: OpenDatabaseOptions(readOnly: true))`.58 * `deleteDatabase(path)` and `getDatabasesPath()` (default is59 `<cwd>/.dart_tool/sqflite_common_ffi/databases`, relative paths resolve60 there).61* The parent directory of a database file is created on open. Prefer absolute62 paths in applications.63* Limitations (from the package README and source):64 * `singleInstance` is ignored: `sqlite_async` manages opening/closing.65 * No logger support (`SqfliteLoggerDatabaseFactory` wrappers are not66 wired in).67 * `queryCursor` / `rawQueryCursor` load the whole result set, no paging.68 * io only (`platforms: linux, macos, windows, android, ios`); no web.69 Calling on the web throws `UnsupportedError`.70 * After `close()`, any call fails with a `database_closed` error.71* For unit tests prefer `databaseFactoryFfi` from `sqflite_common_ffi` (see72 `sqflite-common-ffi-testing`) unless the test exercises `readTransaction`73 or concurrency behavior; then use `databaseFactoryFfiAsyncTest` with a74 file path and `deleteDatabase` in `setUp`.7576## Examples7778### Concurrent read during a long write transaction7980```dart81import 'package:sqflite_common_ffi/sqflite_ffi.dart' show sqfliteFfiInit;82import 'package:sqflite_common_ffi_async/sqflite_ffi_async.dart';8384Future<void> main() async {85 sqfliteFfiInit();86 var factory = databaseFactoryFfiAsync;87 var db = await factory.openDatabase(88 'concurrent.db',89 options: OpenDatabaseOptions(90 version: 1,91 onCreate: (db, _) => db.execute(92 'CREATE TABLE Item (id INTEGER PRIMARY KEY, name TEXT)',93 ),94 ),95 );9697 var write = db.transaction((txn) async {98 await txn.insert('Item', {'name': 'slow'});99 await Future<void>.delayed(const Duration(milliseconds: 500));100 await txn.insert('Item', {'name': 'write'});101 });102 // Runs on a reader connection while the write transaction is open.103 var count = await db.readTransaction((txn) async {104 var rows = await txn.rawQuery('SELECT COUNT(*) AS c FROM Item');105 return rows.first['c'] as int;106 });107 await write;108 print(count); // 0: the write transaction had not committed yet109 await db.close();110}111```112113### Test with the dedicated test factory114115```dart116@TestOn('vm')117library;118119import 'package:sqflite_common_ffi/sqflite_ffi.dart' show sqfliteFfiInit;120import 'package:sqflite_common_ffi_async/sqflite_ffi_async.dart';121import 'package:test/test.dart';122123void main() {124 sqfliteFfiInit();125 final factory = databaseFactoryFfiAsyncTest;126 const path = 'ffi_async_test.db';127128 setUp(() => factory.deleteDatabase(path));129130 test('version and insert', () async {131 var db = await factory.openDatabase(132 path,133 options: OpenDatabaseOptions(134 version: 1,135 onCreate: (db, _) => db.execute('CREATE TABLE Test (id INTEGER PRIMARY KEY)'),136 ),137 );138 expect(await db.getVersion(), 1);139 expect(await db.insert('Test', {'id': 1}), 1);140 await db.close();141 });142}143```144145### Selecting the factory per platform146147```dart148import 'dart:io';149150import 'package:sqflite_common_ffi/sqflite_ffi.dart';151import 'package:sqflite_common_ffi_async/sqflite_ffi_async.dart';152153DatabaseFactory pickFactory() {154 sqfliteFfiInit();155 if (Platform.isLinux || Platform.isMacOS || Platform.isWindows) {156 return databaseFactoryFfiAsync;157 }158 return databaseFactoryFfi;159}160```161162## Common mistakes163164* Writing inside `readTransaction`: throws. Use `transaction` for writes.165* Using `readTransaction` with `databaseFactoryFfi` or the `sqflite` plugin:166 not supported there.167* Expecting `singleInstance: true` semantics (same `Database` object for the168 same path): the async factory does not honor it.169* Forgetting `sqfliteFfiInit()` on Windows.170* Using it on the web: use `sqflite_common_ffi_web` instead.171* Depending only on `sqflite_common_ffi_async` and importing172 `sqflite_common_ffi/sqflite_ffi.dart` transitively; declare both.