Skip to main content

sqflite-testing-and-platforms

Use when unit testing or widget testing code that uses package:sqflite, running it on Linux/Windows/Dart VM (sqflite_common_ffi: sqfliteFfiInit, databaseFactoryFfi, databaseFactoryFfiNoIsolate) or on the web (sqflite_common_ffi_web: databaseFactoryFfiWeb), swapping the global databaseFactory, and debugging SQL: SqfliteDatabaseFactoryLogger, debugQuickLoggerWrapper, Sqflite.setDebugModeOn, setLogLevel, setLockWarningInfo, "database has been locked", MissingPluginException, isolates, Android WAL manifest, iOS unprotected folder, encryption.

インストールへ移動

ソース情報

リポジトリ
tekartik/sqflite
ソースの最終更新活動
2026年9月10日 13:15
検出された SKILL.md の言語
英語
スター
3,019
フォーク
555

インストール方法

デフォルトでは、最初にソースを確認する Prompt が選択されています。直接コマンドに切り替えるか、ローカルコピーをダウンロードすることもできます。

ソースファイルを確認

インストールを決める前に、SKILL.md と SkillsMP に表示されている付属ファイルをお読みください。

SKILL.md を表示中

SKILL.md
ソースの指示 · 読み取り専用プレビュー
name
sqflite-testing-and-platforms
description
Use when unit testing or widget testing code that uses package:sqflite, running it on Linux/Windows/Dart VM (sqflite_common_ffi: sqfliteFfiInit, databaseFactoryFfi, databaseFactoryFfiNoIsolate) or on the web (sqflite_common_ffi_web: databaseFactoryFfiWeb), swapping the global databaseFactory, and debugging SQL: SqfliteDatabaseFactoryLogger, debugQuickLoggerWrapper, Sqflite.setDebugModeOn, setLogLevel, setLockWarningInfo, "database has been locked", MissingPluginException, isolates, Android WAL manifest, iOS unprotected folder, encryption.
# sqflite: tests, other platforms and debugging The `sqflite` plugin itself only runs on an Android, iOS or macOS device or simulator: `flutter test` has no native SQLite, so `openDatabase` throws `MissingPluginException`. Every sqflite API is routed through the global `databaseFactory`; point it at another implementation and the rest of the code (`openDatabase`, `Database`, `Transaction`, `Batch`) is unchanged. ```dart // test/db_test.dart import 'package:flutter_test/flutter_test.dart'; // Re-exports openDatabase, databaseFactory, Database... from sqflite_common. import 'package:sqflite_common_ffi/sqflite_ffi.dart'; void main() { sqfliteFfiInit(); databaseFactory = databaseFactoryFfi; test('create and read', () async { final db = await openDatabase(inMemoryDatabasePath); await db.execute('CREATE TABLE Product (id INTEGER PRIMARY KEY, title TEXT)'); await db.insert('Product', {'title': 'Product 1'}); expect(await db.query('Product'), [ {'id': 1, 'title': 'Product 1'}, ]); await db.close(); }); } ``` ## Guidelines ### Which factory where * `sqflite` (Android/iOS/macOS): nothing to do, the plugin registers `databaseFactory` at startup (`databaseFactorySqflitePlugin`). * Linux, Windows, Dart VM, `flutter test`, `dart test`: add `sqflite_common_ffi` (as a `dev_dependency` when only tests need it), call `sqfliteFfiInit()` once, then `databaseFactory = databaseFactoryFfi`. It uses its own SQLite (`package:sqlite3`), usually newer than the device one; run integration tests on a device for version-specific SQL. * Widget tests (`testWidgets`): use `databaseFactoryFfiNoIsolate` instead of `databaseFactoryFfi`; the isolate-based factory hangs in the test binding. * `package:sqflite_common_ffi/sqflite_ffi.dart` re-exports the whole `package:sqflite_common/sqflite.dart` API (`openDatabase`, `databaseFactory`, `Database`, `inMemoryDatabasePath`, ...), so a test that imports it does not need `package:sqflite/sqflite.dart` as well (the analyzer flags it as `unnecessary_import`); import `sqflite.dart` only for the `Sqflite` helper class or the Android/Darwin extensions. * Web: add `sqflite_common_ffi_web`, set `databaseFactory = databaseFactoryFfiWeb` (setup of the worker/wasm binaries is described in that package's own skill/README). * One `main()` can select the factory at runtime: `kIsWeb` first, then `Platform.isWindows || Platform.isLinux`, otherwise leave the sqflite default. Set `databaseFactory` once, before any `openDatabase`, and before `runApp`. Setting it twice prints a warning. * `databaseFactory` throws `StateError('databaseFactory not initialized')` when read before an implementation registered; `databaseFactoryOrNull` is the nullable getter. * Encryption: `sqflite_sqlcipher` on Android/iOS/macOS (same API, shares `sqflite_common`); on desktop see the `sqflite_common_ffi` documentation. ### Writing testable code * Do not call the global `openDatabase` from your repositories. Inject a `DatabaseFactory` (or an already open `Database`) and open with `factory.openDatabase(path, options: OpenDatabaseOptions(...))`. Tests pass `databaseFactoryFfi`, the app passes `databaseFactory`. * Use `inMemoryDatabasePath` for fast, isolated tests (no file, no single-instance sharing). To test file behaviour use a temp directory and `factory.deleteDatabase(path)` in `setUp`. * `factory.sandbox(path: dir)` returns a factory confined to `dir`, handy to keep a test's databases apart from the app's. * Run migration tests by opening at `version: 1`, closing, then reopening at `version: 2` on the same path and asserting the schema (`PRAGMA table_info(...)` or `sqlite_master`). * `package:sqflite/sqflite_dev.dart` exposes `setMockDatabaseFactory(factory)` (test only) and `sqfliteDatabaseFactoryDefault`; prefer assigning `databaseFactory` directly. ### Debugging SQL * Wrap any factory with `SqfliteDatabaseFactoryLogger` (annotated `@experimental`, the analyzer reports `experimental_member_use`) from `package:sqflite_common/sqflite_logger.dart` to see every statement, its arguments, result and duration: `databaseFactory = SqfliteDatabaseFactoryLogger(databaseFactory, options: SqfliteLoggerOptions(type: SqfliteDatabaseFactoryLoggerType.all, log: (event) {...}))`. Events are `SqfliteLoggerSqlEvent` (`sql`, `arguments`, `result`, `error`, `sw`), `SqfliteLoggerBatchEvent` (`operations`), `SqfliteLoggerDatabaseOpenEvent`, `...CloseEvent`, `...DeleteEvent`, `SqfliteLoggerInvokeEvent`. `event.dump()` prints the event (default `log`). * `databaseFactory = databaseFactory.debugQuickLoggerWrapper()` is the one-liner form (deprecated on purpose so it is not left in production). * Native-side logs: `await databaseFactory.debugSetLogLevel( sqfliteLogLevelVerbose)` (extension `SqfliteDatabaseFactoryDebug`, deprecated on purpose) before opening; levels `sqfliteLogLevelNone`, `sqfliteLogLevelSql`, `sqfliteLogLevelVerbose`. `Sqflite.setDebugModeOn()` / `Sqflite.devSetDebugModeOn()` are the older deprecated equivalents. * `Sqflite.setLockWarningInfo(duration:, callback:)` changes the 10 s "Warning database has been locked" watchdog. That warning almost always means a call on `db` inside `db.transaction((txn) ...)`. * Print `await db.query('sqlite_master')` to dump the schema, and `SELECT sqlite_version()` to know which SQL features exist on the device. ### Platform notes * `MissingPluginException` on device: stop and rebuild the app (hot restart does not register a newly added plugin), `flutter clean`, on iOS `pod install`. In an FCM background handler, register plugins early. * Android: WAL is off by default. Enable it with `<meta-data android:name="com.tekartik.sqflite.wal_enabled" android:value="true"/>` in the `<application>` manifest element, or portably with `db.setJournalMode('WAL')` in `onConfigure`. Read-only opens do not delete a corrupt file; read-write opens follow Android's default handler (the file is removed). * iOS background isolate while the device is locked: create the database in a folder created by `SqfliteDarwin.createUnprotectedFolder(parent, name)` (data protection `NSFileProtectionNone`). * Isolates: use the main isolate. A second isolate must open with `singleInstance: false` and must not close the database. * Rows are limited to about 1 MB on Android/iOS cursors; big blobs belong in files. ## Examples ### Repository that takes a factory, tested with ffi ```dart // lib/todo_repository.dart import 'package:sqflite/sqflite.dart'; class TodoRepository { TodoRepository(this.factory, this.path); final DatabaseFactory factory; final String path; Future<Database>? _db; Future<Database> get db => _db ??= factory.openDatabase( path, options: OpenDatabaseOptions( version: 1, onCreate: (db, _) => db.execute( 'CREATE TABLE Todo (id INTEGER PRIMARY KEY, title TEXT NOT NULL)', ), ), ); Future<int> add(String title) async => (await db).insert('Todo', {'title': title}); Future<List<String>> titles() async => (await (await db).query('Todo', orderBy: 'id')) .map((row) => row['title'] as String) .toList(); Future<void> close() async { await (await db).close(); _db = null; } } ``` ```dart // test/todo_repository_test.dart import 'package:flutter_test/flutter_test.dart'; import 'package:sqflite_common_ffi/sqflite_ffi.dart'; import 'package:my_app/todo_repository.dart'; void main() { setUpAll(sqfliteFfiInit); test('add and list', () async { final repo = TodoRepository(databaseFactoryFfi, inMemoryDatabasePath); await repo.add('a'); await repo.add('b'); expect(await repo.titles(), ['a', 'b']); await repo.close(); }); } ``` ### Widget test ```dart import 'package:flutter_test/flutter_test.dart'; import 'package:sqflite_common_ffi/sqflite_ffi.dart'; void main() { sqfliteFfiInit(); // No isolate inside the widget test binding. databaseFactory = databaseFactoryFfiNoIsolate; testWidgets('database in a widget test', (tester) async { final db = await openDatabase( inMemoryDatabasePath, version: 1, onCreate: (db, _) => db.execute('CREATE TABLE Test (id INTEGER PRIMARY KEY, value TEXT)'), ); await db.insert('Test', {'value': 'v'}); expect(await db.query('Test'), [ {'id': 1, 'value': 'v'}, ]); await db.close(); }); } ``` ### Selecting the factory per platform in main() ```dart import 'dart:io'; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/widgets.dart'; // sqflite_ffi.dart already exports databaseFactory; the sqflite plugin // registers itself on Android/iOS/macOS without any import. import 'package:sqflite_common_ffi/sqflite_ffi.dart'; import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart'; Future<void> main() async { WidgetsFlutterBinding.ensureInitialized(); if (kIsWeb) { databaseFactory = databaseFactoryFfiWeb; } else if (Platform.isWindows || Platform.isLinux) { sqfliteFfiInit(); databaseFactory = databaseFactoryFfi; } // Android, iOS, macOS keep the sqflite plugin factory. runApp(const SizedBox()); } ``` ### Logging every statement ```dart import 'package:sqflite/sqflite.dart'; import 'package:sqflite_common/sqflite_logger.dart'; void enableSqlLogs() { // ignore: experimental_member_use databaseFactory = SqfliteDatabaseFactoryLogger( databaseFactory, options: SqfliteLoggerOptions( type: SqfliteDatabaseFactoryLoggerType.all, log: (event) { if (event is SqfliteLoggerSqlEvent) { print('sql: ${event.sql} ${event.arguments ?? ''} ' '${event.error ?? ''} ${event.sw?.elapsed ?? ''}'); } else if (event is SqfliteLoggerBatchEvent) { for (final op in event.operations) { print('batch: ${op.sql} ${op.arguments ?? ''}'); } } else { event.dump(); } }, ), ); } ``` ### Migration test on a file database ```dart import 'dart:io'; import 'package:flutter_test/flutter_test.dart'; import 'package:path/path.dart'; import 'package:sqflite_common_ffi/sqflite_ffi.dart'; void main() { sqfliteFfiInit(); final factory = databaseFactoryFfi; test('v1 to v2 adds a column', () async { final dir = await Directory.systemTemp.createTemp('sqflite_test'); final path = join(dir.path, 'm.db'); await factory.deleteDatabase(path); var db = await factory.openDatabase( path, options: OpenDatabaseOptions( version: 1, onCreate: (db, _) => db.execute('CREATE TABLE T (id INTEGER PRIMARY KEY)'), ), ); await db.close(); db = await factory.openDatabase( path, options: OpenDatabaseOptions( version: 2, onUpgrade: (db, old, _) async { if (old < 2) await db.execute('ALTER TABLE T ADD name TEXT'); }, ), ); final columns = (await db.rawQuery('PRAGMA table_info(T)')) .map((row) => row['name']) .toList(); expect(columns, ['id', 'name']); expect(await db.getVersion(), 2); await db.close(); }); } ``` ## Common mistakes * Running `flutter test` against the plugin factory and getting `MissingPluginException`: set `databaseFactory = databaseFactoryFfi`. * Forgetting `sqfliteFfiInit()` before using `databaseFactoryFfi` on Windows or Linux. * Using `databaseFactoryFfi` (isolate) in `testWidgets`; use `databaseFactoryFfiNoIsolate`. * Setting `databaseFactory` after some code already opened a database, or in a library rather than in the app's `main()`. * Leaving `debugQuickLoggerWrapper()` / `debugSetLogLevel` in production code; they are deprecated so the analyzer flags them. * Reusing an in-memory path between tests and expecting shared state: each `openDatabase(inMemoryDatabasePath)` is a new empty database. ## More Opening and migrations: `sqflite-open-database` skill. Queries and transactions: `sqflite-crud-and-transactions` skill. Pure-Dart API for shared packages: the `sqflite_common` package skill. Desktop and web factories: the `sqflite_common_ffi` and `sqflite_common_ffi_web` package skills.
GitHubで見る