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.
// 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
// 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;
}
}
// 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
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()
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
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
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.
1---2name: sqflite-testing-and-platforms3description: 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.4---56# sqflite: tests, other platforms and debugging78The `sqflite` plugin itself only runs on an Android, iOS or macOS device or9simulator: `flutter test` has no native SQLite, so `openDatabase` throws10`MissingPluginException`. Every sqflite API is routed through the global11`databaseFactory`; point it at another implementation and the rest of the12code (`openDatabase`, `Database`, `Transaction`, `Batch`) is unchanged.1314```dart15// test/db_test.dart16import 'package:flutter_test/flutter_test.dart';17// Re-exports openDatabase, databaseFactory, Database... from sqflite_common.18import 'package:sqflite_common_ffi/sqflite_ffi.dart';1920void main() {21 sqfliteFfiInit();22 databaseFactory = databaseFactoryFfi;2324 test('create and read', () async {25 final db = await openDatabase(inMemoryDatabasePath);26 await db.execute('CREATE TABLE Product (id INTEGER PRIMARY KEY, title TEXT)');27 await db.insert('Product', {'title': 'Product 1'});28 expect(await db.query('Product'), [29 {'id': 1, 'title': 'Product 1'},30 ]);31 await db.close();32 });33}34```3536## Guidelines3738### Which factory where3940* `sqflite` (Android/iOS/macOS): nothing to do, the plugin registers41 `databaseFactory` at startup (`databaseFactorySqflitePlugin`).42* Linux, Windows, Dart VM, `flutter test`, `dart test`: add43 `sqflite_common_ffi` (as a `dev_dependency` when only tests need it), call44 `sqfliteFfiInit()` once, then `databaseFactory = databaseFactoryFfi`. It45 uses its own SQLite (`package:sqlite3`), usually newer than the device46 one; run integration tests on a device for version-specific SQL.47* Widget tests (`testWidgets`): use `databaseFactoryFfiNoIsolate` instead of48 `databaseFactoryFfi`; the isolate-based factory hangs in the test binding.49* `package:sqflite_common_ffi/sqflite_ffi.dart` re-exports the whole50 `package:sqflite_common/sqflite.dart` API (`openDatabase`,51 `databaseFactory`, `Database`, `inMemoryDatabasePath`, ...), so a test52 that imports it does not need `package:sqflite/sqflite.dart` as well (the53 analyzer flags it as `unnecessary_import`); import `sqflite.dart` only for54 the `Sqflite` helper class or the Android/Darwin extensions.55* Web: add `sqflite_common_ffi_web`, set `databaseFactory =56 databaseFactoryFfiWeb` (setup of the worker/wasm binaries is described in57 that package's own skill/README).58* One `main()` can select the factory at runtime: `kIsWeb` first, then59 `Platform.isWindows || Platform.isLinux`, otherwise leave the sqflite60 default. Set `databaseFactory` once, before any `openDatabase`, and before61 `runApp`. Setting it twice prints a warning.62* `databaseFactory` throws `StateError('databaseFactory not initialized')`63 when read before an implementation registered; `databaseFactoryOrNull` is64 the nullable getter.65* Encryption: `sqflite_sqlcipher` on Android/iOS/macOS (same API, shares66 `sqflite_common`); on desktop see the `sqflite_common_ffi` documentation.6768### Writing testable code6970* Do not call the global `openDatabase` from your repositories. Inject a71 `DatabaseFactory` (or an already open `Database`) and open with72 `factory.openDatabase(path, options: OpenDatabaseOptions(...))`. Tests pass73 `databaseFactoryFfi`, the app passes `databaseFactory`.74* Use `inMemoryDatabasePath` for fast, isolated tests (no file, no75 single-instance sharing). To test file behaviour use a temp directory and76 `factory.deleteDatabase(path)` in `setUp`.77* `factory.sandbox(path: dir)` returns a factory confined to `dir`, handy to78 keep a test's databases apart from the app's.79* Run migration tests by opening at `version: 1`, closing, then reopening at80 `version: 2` on the same path and asserting the schema81 (`PRAGMA table_info(...)` or `sqlite_master`).82* `package:sqflite/sqflite_dev.dart` exposes `setMockDatabaseFactory(factory)`83 (test only) and `sqfliteDatabaseFactoryDefault`; prefer assigning84 `databaseFactory` directly.8586### Debugging SQL8788* Wrap any factory with `SqfliteDatabaseFactoryLogger` (annotated89 `@experimental`, the analyzer reports `experimental_member_use`) from90 `package:sqflite_common/sqflite_logger.dart` to see every statement, its91 arguments, result and duration: `databaseFactory =92 SqfliteDatabaseFactoryLogger(databaseFactory, options:93 SqfliteLoggerOptions(type: SqfliteDatabaseFactoryLoggerType.all, log:94 (event) {...}))`. Events are `SqfliteLoggerSqlEvent` (`sql`, `arguments`,95 `result`, `error`, `sw`), `SqfliteLoggerBatchEvent` (`operations`),96 `SqfliteLoggerDatabaseOpenEvent`, `...CloseEvent`, `...DeleteEvent`,97 `SqfliteLoggerInvokeEvent`. `event.dump()` prints the event (default98 `log`).99* `databaseFactory = databaseFactory.debugQuickLoggerWrapper()` is the100 one-liner form (deprecated on purpose so it is not left in production).101* Native-side logs: `await databaseFactory.debugSetLogLevel(102 sqfliteLogLevelVerbose)` (extension `SqfliteDatabaseFactoryDebug`,103 deprecated on purpose) before opening; levels `sqfliteLogLevelNone`,104 `sqfliteLogLevelSql`, `sqfliteLogLevelVerbose`. `Sqflite.setDebugModeOn()`105 / `Sqflite.devSetDebugModeOn()` are the older deprecated equivalents.106* `Sqflite.setLockWarningInfo(duration:, callback:)` changes the 10 s107 "Warning database has been locked" watchdog. That warning almost always108 means a call on `db` inside `db.transaction((txn) ...)`.109* Print `await db.query('sqlite_master')` to dump the schema, and110 `SELECT sqlite_version()` to know which SQL features exist on the device.111112### Platform notes113114* `MissingPluginException` on device: stop and rebuild the app (hot115 restart does not register a newly added plugin), `flutter clean`, on iOS116 `pod install`. In an FCM background handler, register plugins early.117* Android: WAL is off by default. Enable it with `<meta-data118 android:name="com.tekartik.sqflite.wal_enabled" android:value="true"/>` in119 the `<application>` manifest element, or portably with120 `db.setJournalMode('WAL')` in `onConfigure`. Read-only opens do not121 delete a corrupt file; read-write opens follow Android's default handler122 (the file is removed).123* iOS background isolate while the device is locked: create the database in124 a folder created by `SqfliteDarwin.createUnprotectedFolder(parent, name)`125 (data protection `NSFileProtectionNone`).126* Isolates: use the main isolate. A second isolate must open with127 `singleInstance: false` and must not close the database.128* Rows are limited to about 1 MB on Android/iOS cursors; big blobs belong in129 files.130131## Examples132133### Repository that takes a factory, tested with ffi134135```dart136// lib/todo_repository.dart137import 'package:sqflite/sqflite.dart';138139class TodoRepository {140 TodoRepository(this.factory, this.path);141142 final DatabaseFactory factory;143 final String path;144 Future<Database>? _db;145146 Future<Database> get db => _db ??= factory.openDatabase(147 path,148 options: OpenDatabaseOptions(149 version: 1,150 onCreate: (db, _) => db.execute(151 'CREATE TABLE Todo (id INTEGER PRIMARY KEY, title TEXT NOT NULL)',152 ),153 ),154 );155156 Future<int> add(String title) async =>157 (await db).insert('Todo', {'title': title});158159 Future<List<String>> titles() async => (await (await db).query('Todo', orderBy: 'id'))160 .map((row) => row['title'] as String)161 .toList();162163 Future<void> close() async {164 await (await db).close();165 _db = null;166 }167}168```169170```dart171// test/todo_repository_test.dart172import 'package:flutter_test/flutter_test.dart';173import 'package:sqflite_common_ffi/sqflite_ffi.dart';174175import 'package:my_app/todo_repository.dart';176177void main() {178 setUpAll(sqfliteFfiInit);179180 test('add and list', () async {181 final repo = TodoRepository(databaseFactoryFfi, inMemoryDatabasePath);182 await repo.add('a');183 await repo.add('b');184 expect(await repo.titles(), ['a', 'b']);185 await repo.close();186 });187}188```189190### Widget test191192```dart193import 'package:flutter_test/flutter_test.dart';194import 'package:sqflite_common_ffi/sqflite_ffi.dart';195196void main() {197 sqfliteFfiInit();198 // No isolate inside the widget test binding.199 databaseFactory = databaseFactoryFfiNoIsolate;200201 testWidgets('database in a widget test', (tester) async {202 final db = await openDatabase(203 inMemoryDatabasePath,204 version: 1,205 onCreate: (db, _) =>206 db.execute('CREATE TABLE Test (id INTEGER PRIMARY KEY, value TEXT)'),207 );208 await db.insert('Test', {'value': 'v'});209 expect(await db.query('Test'), [210 {'id': 1, 'value': 'v'},211 ]);212 await db.close();213 });214}215```216217### Selecting the factory per platform in main()218219```dart220import 'dart:io';221222import 'package:flutter/foundation.dart' show kIsWeb;223import 'package:flutter/widgets.dart';224// sqflite_ffi.dart already exports databaseFactory; the sqflite plugin225// registers itself on Android/iOS/macOS without any import.226import 'package:sqflite_common_ffi/sqflite_ffi.dart';227import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart';228229Future<void> main() async {230 WidgetsFlutterBinding.ensureInitialized();231 if (kIsWeb) {232 databaseFactory = databaseFactoryFfiWeb;233 } else if (Platform.isWindows || Platform.isLinux) {234 sqfliteFfiInit();235 databaseFactory = databaseFactoryFfi;236 }237 // Android, iOS, macOS keep the sqflite plugin factory.238 runApp(const SizedBox());239}240```241242### Logging every statement243244```dart245import 'package:sqflite/sqflite.dart';246import 'package:sqflite_common/sqflite_logger.dart';247248void enableSqlLogs() {249 // ignore: experimental_member_use250 databaseFactory = SqfliteDatabaseFactoryLogger(251 databaseFactory,252 options: SqfliteLoggerOptions(253 type: SqfliteDatabaseFactoryLoggerType.all,254 log: (event) {255 if (event is SqfliteLoggerSqlEvent) {256 print('sql: ${event.sql} ${event.arguments ?? ''} '257 '${event.error ?? ''} ${event.sw?.elapsed ?? ''}');258 } else if (event is SqfliteLoggerBatchEvent) {259 for (final op in event.operations) {260 print('batch: ${op.sql} ${op.arguments ?? ''}');261 }262 } else {263 event.dump();264 }265 },266 ),267 );268}269```270271### Migration test on a file database272273```dart274import 'dart:io';275276import 'package:flutter_test/flutter_test.dart';277import 'package:path/path.dart';278import 'package:sqflite_common_ffi/sqflite_ffi.dart';279280void main() {281 sqfliteFfiInit();282 final factory = databaseFactoryFfi;283284 test('v1 to v2 adds a column', () async {285 final dir = await Directory.systemTemp.createTemp('sqflite_test');286 final path = join(dir.path, 'm.db');287 await factory.deleteDatabase(path);288289 var db = await factory.openDatabase(290 path,291 options: OpenDatabaseOptions(292 version: 1,293 onCreate: (db, _) => db.execute('CREATE TABLE T (id INTEGER PRIMARY KEY)'),294 ),295 );296 await db.close();297298 db = await factory.openDatabase(299 path,300 options: OpenDatabaseOptions(301 version: 2,302 onUpgrade: (db, old, _) async {303 if (old < 2) await db.execute('ALTER TABLE T ADD name TEXT');304 },305 ),306 );307 final columns = (await db.rawQuery('PRAGMA table_info(T)'))308 .map((row) => row['name'])309 .toList();310 expect(columns, ['id', 'name']);311 expect(await db.getVersion(), 2);312 await db.close();313 });314}315```316317## Common mistakes318319* Running `flutter test` against the plugin factory and getting320 `MissingPluginException`: set `databaseFactory = databaseFactoryFfi`.321* Forgetting `sqfliteFfiInit()` before using `databaseFactoryFfi` on Windows322 or Linux.323* Using `databaseFactoryFfi` (isolate) in `testWidgets`; use324 `databaseFactoryFfiNoIsolate`.325* Setting `databaseFactory` after some code already opened a database, or326 in a library rather than in the app's `main()`.327* Leaving `debugQuickLoggerWrapper()` / `debugSetLogLevel` in production328 code; they are deprecated so the analyzer flags them.329* Reusing an in-memory path between tests and expecting shared state: each330 `openDatabase(inMemoryDatabasePath)` is a new empty database.331332## More333334Opening and migrations: `sqflite-open-database` skill. Queries and335transactions: `sqflite-crud-and-transactions` skill. Pure-Dart API for shared336packages: the `sqflite_common` package skill. Desktop and web factories: the337`sqflite_common_ffi` and `sqflite_common_ffi_web` package skills.