Agent Skillstekartik/sqflite › sqflite-testing-and-platforms

sqflite-testing-and-platforms

GitHub

提供 sqflite 在 Flutter 测试环境中的配置方案,支持 Linux/Windows/Web 平台通过 FFI 或 Web 工厂运行单元测试和组件测试,并包含 SQL 调试与日志记录工具。

sqflite/skills/sqflite-testing-and-platforms/SKILL.md tekartik/sqflite

Trigger Scenarios

Flutter 单元测试中使用 sqflite Widget 测试中配置 SQLite 数据库 在非移动平台(如 Linux、Web)运行 sqflite 代码 调试 SQLite 查询或锁定问题

Install

npx skills add tekartik/sqflite --skill sqflite-testing-and-platforms -g -y
More Options

Non-standard path

npx skills add https://github.com/tekartik/sqflite/tree/master/sqflite/skills/sqflite-testing-and-platforms -g -y

Use without installing

npx skills use tekartik/sqflite@sqflite-testing-and-platforms

指定 Agent (Claude Code)

npx skills add tekartik/sqflite --skill sqflite-testing-and-platforms -a claude-code -g -y

安装 repo 全部 skill

npx skills add tekartik/sqflite --all -g -y

预览 repo 内 skill

npx skills add tekartik/sqflite --list

SKILL.md

Frontmatter
{
    "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.

// 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.

Version History

  • aaabf90 Current 2026-09-22 03:38

Same Skill Collection

packages/sqflite_common_ffi_async_test/skills/sqflite-common-ffi-async-test-suite/SKILL.md
packages/sqflite_common_ffi_async/skills/sqflite-common-ffi-async-factory/SKILL.md
sqflite_android/skills/sqflite-android-setup/SKILL.md
sqflite_common_ffi/skills/sqflite-common-ffi-testing/SKILL.md
packages_flutter/sqflite_ffi/skills/sqflite-ffi-flutter/SKILL.md
packages_web/sqflite_common_ffi_web_test/skills/sqflite-common-ffi-web-test-suite/SKILL.md
packages_web/sqflite_common_ffi_web/skills/sqflite-common-ffi-web-options/SKILL.md
packages_web/sqflite_common_ffi_web/skills/sqflite-common-ffi-web-setup/SKILL.md
sqflite_common_ffi/skills/sqflite-common-ffi-desktop/SKILL.md
sqflite_common_test/skills/sqflite-common-test-suite/SKILL.md
sqflite_common/skills/sqflite-common-api/SKILL.md
sqflite_darwin/skills/sqflite-darwin-setup/SKILL.md
sqflite_platform_interface/skills/sqflite-platform-interface-implementers/SKILL.md
sqflite/skills/sqflite-crud-and-transactions/SKILL.md
sqflite/skills/sqflite-open-database/SKILL.md

Metadata

Files
0
Version
aaabf90
Hash
81e43a21
Indexed
2026-09-22 03:38

Accueil - Wiki
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-09-22 09:59
浙ICP备14020137号-1