Skip to main content

sqflite-ffi-flutter

Use when a Flutter app should use the ffi (package:sqlite3) implementation of sqflite on desktop and mobile with package:sqflite_ffi: what it adds over sqflite_common_ffi (Dart-only plugin, automatic registration of sqfliteDatabaseFactoryFfi as the default databaseFactory through SqfliteFfiPlugin.registerWith, one sqflite isolate shared between Flutter isolates via IsolateNameServer and sqfliteFfiIsolatePortName), createSqfliteDatabaseFactoryFfi, using the database from compute / Isolate.run, DartPluginRegistrant.ensureInitialized, coexistence with the native sqflite plugin, and when to pick sqflite, sqflite_common_ffi or sqflite_ffi.

Aller à l'installation

Informations de source

Dépôt
tekartik/sqflite
Dernière activité de la source
10 septembre 2026 à 13:15
Langue détectée de SKILL.md
anglais
Étoiles
3 019
Forks
555

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
sqflite-ffi-flutter
description
Use when a Flutter app should use the ffi (package:sqlite3) implementation of sqflite on desktop and mobile with package:sqflite_ffi: what it adds over sqflite_common_ffi (Dart-only plugin, automatic registration of sqfliteDatabaseFactoryFfi as the default databaseFactory through SqfliteFfiPlugin.registerWith, one sqflite isolate shared between Flutter isolates via IsolateNameServer and sqfliteFfiIsolatePortName), createSqfliteDatabaseFactoryFfi, using the database from compute / Isolate.run, DartPluginRegistrant.ensureInitialized, coexistence with the native sqflite plugin, and when to pick sqflite, sqflite_common_ffi or sqflite_ffi.
# sqflite_ffi: ffi sqflite as a Flutter plugin `package:sqflite_ffi` wraps `sqflite_common_ffi` in a Dart-only Flutter plugin. Adding it to a Flutter app does two things that `sqflite_common_ffi` alone does not: * At startup Flutter calls `SqfliteFfiPlugin.registerWith()`, which runs `sqfliteFfiInit()` and sets `sqfliteDatabaseFactoryFfi` as the global `databaseFactory` if none is registered yet. The global `openDatabase()` works on Windows, Linux, macOS, Android and iOS with no `main()` code. * All Flutter isolates (main, `compute`, `Isolate.run`) share one sqflite isolate: its `SendPort` is registered in `IsolateNameServer` under `sqfliteFfiIsolatePortName`, so `singleInstance` and transaction ordering hold across isolates. ```dart import 'package:flutter/widgets.dart'; import 'package:sqflite_ffi/sqflite_ffi.dart'; Future<void> main() async { WidgetsFlutterBinding.ensureInitialized(); var db = await openDatabase(inMemoryDatabasePath); debugPrint('${await db.rawQuery('SELECT sqlite_version()')}'); await db.close(); runApp(const SizedBox()); } ``` ## Guidelines * Add `sqflite_ffi` (0.1.x, Flutter >= 3.44, Dart 3.12) to `dependencies`. It brings `sqflite_common_ffi` and `sqlite3` (>= 3, build hooks: SQLite is bundled, nothing to install; run `flutter clean` when changing the `sqlite3` major version). * Import `package:sqflite_ffi/sqflite_ffi.dart`. It re-exports `sqflite_common_ffi/sqflite_ffi.dart` (hence the whole `sqflite_common` API: `Database`, `openDatabase`, `deleteDatabase`, `inMemoryDatabasePath`, `databaseFactory`, `sqfliteFfiInit`, `SqfliteFfiInit`, `databaseFactoryFfiNoIsolate`, `SqfliteFfiIsolatePortServer`) but hides `databaseFactoryFfi` and `createDatabaseFactoryFfi`. Use the package's own `sqfliteDatabaseFactoryFfi` and `createSqfliteDatabaseFactoryFfi` instead. * Choosing between packages: * `sqflite` alone: native plugin, iOS/Android/macOS only. * `sqflite_common_ffi`: pure Dart, desktop and tests; you set `databaseFactory` yourself; each Dart isolate gets its own sqflite isolate. * `sqflite_ffi`: Flutter app that wants ffi everywhere (or on desktop with zero setup) and/or uses the database from several isolates. * `sqflite_common_ffi_web` for the web: `sqflite_ffi` is a no-op there (`SqfliteFfiPlugin.registerWith()` does nothing and `sqfliteDatabaseFactoryFfi` returns the unsupported ffi factory). * Registration order: `SqfliteFfiPlugin.registerWith()` uses `databaseFactoryOrNull ??=`, and so does the native `sqflite` plugin (`SqflitePlugin.registerWith()`). When both plugins are in the app the first one in the generated plugin registrant wins, which is not something to rely on. Assign the factory explicitly in `main()`: `databaseFactory = sqfliteDatabaseFactoryFfi;` to force ffi, or `databaseFactory = databaseFactorySqflitePlugin;` (exported by `package:sqflite/sqflite.dart`) to force the native plugin on mobile. * Explicit factory use is always possible: `sqfliteDatabaseFactoryFfi.openDatabase(path, options: ...)`. Prefer it in libraries and inject it for tests. * `createSqfliteDatabaseFactoryFfi({SqfliteFfiInit? ffiInit})` returns a new factory that still shares the isolate through `IsolateNameServer`. `ffiInit` must be top-level or static and runs in the sqflite isolate before the first SQLite call; the native library itself is selected by the `sqlite3` build hook user defines in `pubspec.yaml` (`hooks.user_defines.sqlite3.source`), see `sqflite-common-ffi-desktop`. * Do not assume plugin registration in background isolates (`compute`, `Isolate.run`, `Isolate.spawn`): the global `databaseFactory` may be unset there. Either call `DartPluginRegistrant.ensureInitialized()` (from `dart:ui`) before the global `openDatabase()`, or use `sqfliteDatabaseFactoryFfi` directly: the `IsolateNameServer` lookup works in any isolate without registration. * When two isolates open the same file, pass `OpenDatabaseOptions(rollbackActiveTransactionOnOpen: false)` (the default is `true` in debug mode) so the second `openDatabase` does not roll back a transaction running in the first isolate. Do not `close()` a shared single-instance database from the background isolate; the owner closes it. * Stale registrations (hot restart leaves a dead port in `IsolateNameServer`) are detected with a ping (2 s timeout) and replaced automatically; no code needed. * Paths: relative paths resolve under `.dart_tool/sqflite_common_ffi/databases` in the current directory. In an app use `path_provider` (`getApplicationSupportDirectory()`) and `package:path` `join`. The parent directory is created on open. * Tests: `flutter test` works with `sqfliteDatabaseFactoryFfi` after `TestWidgetsFlutterBinding.ensureInitialized()` and `sqfliteFfiInit()`; plain `sqflite_common_ffi` in `dev_dependencies` is enough when the test does not need isolate sharing (see `sqflite-common-ffi-testing`). ## Examples ### Database work in a compute isolate sharing the instance ```dart import 'package:flutter/foundation.dart' show compute; import 'package:sqflite_ffi/sqflite_ffi.dart'; Future<void> _insertInIsolate(String path) async { // Same sqflite isolate as the main isolate: same Database instance. final db = await sqfliteDatabaseFactoryFfi.openDatabase( path, options: OpenDatabaseOptions(rollbackActiveTransactionOnOpen: false), ); await db.transaction((txn) async { await txn.insert('Test', {'name': 'isolate 1'}); await txn.insert('Test', {'name': 'isolate 2'}); }); // Do not close: the main isolate owns the shared instance. } Future<List<Object?>> run(String path) async { final db = await sqfliteDatabaseFactoryFfi.openDatabase( path, options: OpenDatabaseOptions( version: 1, onCreate: (db, _) => db.execute( 'CREATE TABLE Test (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)', ), rollbackActiveTransactionOnOpen: false, ), ); try { await Future.wait([ db.transaction((txn) async { await txn.insert('Test', {'name': 'main 1'}); await Future<void>.delayed(const Duration(milliseconds: 100)); await txn.insert('Test', {'name': 'main 2'}); }), compute(_insertInIsolate, path), ]); // The background transaction waited for the main one: // main 1, main 2, isolate 1, isolate 2 return (await db.query('Test', orderBy: 'id')).map((r) => r['name']).toList(); } finally { await db.close(); } } ``` ### Forcing ffi even when the native sqflite plugin is present ```dart import 'package:flutter/widgets.dart'; import 'package:sqflite_ffi/sqflite_ffi.dart'; Future<void> main() async { WidgetsFlutterBinding.ensureInitialized(); // Both sqflite and sqflite_ffi are in pubspec.yaml; pick ffi explicitly. databaseFactory = sqfliteDatabaseFactoryFfi; runApp(const SizedBox()); } ``` ### Database path with path_provider ```dart import 'package:path/path.dart' as p; import 'package:path_provider/path_provider.dart'; import 'package:sqflite_ffi/sqflite_ffi.dart'; Future<Database> openAppDatabase() async { final dir = await getApplicationSupportDirectory(); return openDatabase( p.join(dir.path, 'app.db'), version: 1, onCreate: (db, _) => db.execute( 'CREATE TABLE Note (id INTEGER PRIMARY KEY, text TEXT)', ), ); } ``` ### Isolate spawned outside Flutter ```dart import 'dart:isolate'; import 'dart:ui' show DartPluginRegistrant; import 'package:sqflite_ffi/sqflite_ffi.dart'; Future<int> countInIsolate(String path) => Isolate.run(() async { // Needed for the global openDatabase(); not for sqfliteDatabaseFactoryFfi. DartPluginRegistrant.ensureInitialized(); final db = await openDatabase( path, options: OpenDatabaseOptions(rollbackActiveTransactionOnOpen: false), ); final rows = await db.rawQuery('SELECT COUNT(*) AS c FROM Note'); return rows.first['c'] as int; }); ``` ### Custom ffiInit ```dart import 'package:sqflite_ffi/sqflite_ffi.dart'; void _ffiInit() { // Runs inside the shared sqflite isolate before the first SQLite call. } final myFactory = createSqfliteDatabaseFactoryFfi(ffiInit: _ffiInit); ``` ### Flutter test ```dart import 'package:flutter_test/flutter_test.dart'; import 'package:sqflite_ffi/sqflite_ffi.dart'; void main() { TestWidgetsFlutterBinding.ensureInitialized(); sqfliteFfiInit(); test('open in memory', () async { final db = await sqfliteDatabaseFactoryFfi.openDatabase(inMemoryDatabasePath); expect(await db.getVersion(), 0); await db.close(); }); } ``` ## Common mistakes * Depending on `sqflite_ffi` and still calling `sqfliteFfiInit()` + `databaseFactory = databaseFactoryFfi` from `sqflite_common_ffi`: this creates a second, non-shared sqflite isolate and prints the "changing sqflite default factory" warning. Use `sqfliteDatabaseFactoryFfi` or nothing. * Expecting ffi on the web: use `sqflite_common_ffi_web`. * Closing a shared single-instance database from a background isolate. * Opening the same file from two isolates in debug mode without `rollbackActiveTransactionOnOpen: false`. * Calling `openDatabase()` in a hand-spawned isolate without `DartPluginRegistrant.ensureInitialized()`: `databaseFactory not initialized`. * Having both `sqflite` and `sqflite_ffi` in `pubspec.yaml` and not assigning `databaseFactory`: which implementation runs depends on the registrant order.
Voir sur GitHub