Agent Skillstekartik/sqflite › sqflite-open-database

sqflite-open-database

GitHub

Flutter中使用sqflite插件操作SQLite数据库,涵盖打开、创建、迁移、关闭及删除等操作,处理版本迁移与常见陷阱。

sqflite/skills/sqflite-open-database/SKILL.md tekartik/sqflite

Trigger Scenarios

需要初始化或打开SQLite数据库 执行数据库表结构变更或版本升级 处理数据库文件路径与生命周期管理

Install

npx skills add tekartik/sqflite --skill sqflite-open-database -g -y
More Options

Non-standard path

npx skills add https://github.com/tekartik/sqflite/tree/master/sqflite/skills/sqflite-open-database -g -y

Use without installing

npx skills use tekartik/sqflite@sqflite-open-database

指定 Agent (Claude Code)

npx skills add tekartik/sqflite --skill sqflite-open-database -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-open-database",
    "description": "Use when opening, creating, migrating, closing or deleting a SQLite database with package:sqflite in a Flutter app (Android, iOS, macOS): openDatabase, openReadOnlyDatabase, OpenDatabaseOptions, version, onConfigure, onCreate, onUpgrade, onDowngrade, onDatabaseDowngradeDelete, onOpen, getDatabasesPath, deleteDatabase, databaseExists, inMemoryDatabasePath, singleInstance, readOnly, databaseFactory, getVersion, setJournalMode (WAL), foreign keys, copying an asset database, and the \"database is locked\" pitfalls."
}

sqflite: opening and migrating a database

package:sqflite is the Flutter plugin for SQLite on Android, iOS and macOS. A database is a file identified by a path; a relative path is resolved against getDatabasesPath(). openDatabase runs a small version-based migration mechanism (onCreate / onUpgrade / onDowngrade) inside a transaction and returns a Database that you keep open for the life of the app.

import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';

Future<Database> openAppDatabase() async {
  final path = join(await getDatabasesPath(), 'app.db');
  return openDatabase(
    path,
    version: 1,
    onConfigure: (db) => db.execute('PRAGMA foreign_keys = ON'),
    onCreate: (db, version) async {
      await db.execute(
        'CREATE TABLE Todo (id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, done INTEGER NOT NULL)',
      );
    },
  );
}

Guidelines

Imports and platforms

  • import 'package:sqflite/sqflite.dart'; gives the global functions (openDatabase, openReadOnlyDatabase, getDatabasesPath, deleteDatabase, databaseExists), the global databaseFactory, the Sqflite helper class and every type of package:sqflite/sqlite_api.dart (Database, Transaction, Batch, DatabaseFactory, OpenDatabaseOptions, ConflictAlgorithm, DatabaseException, inMemoryDatabasePath, ...).
  • sqflite only works on Android, iOS and macOS. For Linux, Windows, the Dart VM and unit tests use package:sqflite_common_ffi; for the web use package:sqflite_common_ffi_web; both plug in through databaseFactory = ... and the code below stays unchanged. Code that must not depend on Flutter should import package:sqflite_common/sqlite_api.dart and receive a DatabaseFactory.
  • Build paths with join from package:path, never with string concatenation. getDatabasesPath() is data/data/<package>/databases on Android and the Documents directory on iOS/macOS; on iOS the Library directory from path_provider (getLibraryDirectory()) is the recommended location instead.
  • The plugin creates the parent directory of a read-write database on open. When you write the file yourself (asset copy) create the directory first with Directory(dirname(path)).create(recursive: true).

Versioning callbacks

  • Pass version (an int > 0) to enable migrations. Callbacks run in this order: onConfigure, then exactly one of onCreate / onUpgrade / onDowngrade, then onOpen. Without version only onConfigure and onOpen run.
  • onCreate(db, version) runs when the file does not exist. onUpgrade(db, oldVersion, newVersion) runs when the stored version is lower than version (and also instead of onCreate, with oldVersion == 0, when no onCreate is given). onDowngrade runs when the stored version is higher; pass onDatabaseDowngradeDelete to delete and recreate the database in that case, or onDatabaseVersionChangeError to fail.
  • onCreate, onUpgrade and onDowngrade already run inside a transaction: use the db they receive directly (or db.batch() + commit()), never call db.transaction() inside them. The version is stored (PRAGMA user_version) when the callback completes without throwing.
  • Write migrations as a chain: if (oldVersion < 2) {...} if (oldVersion < 3) {...} so any old version reaches the newest schema. Put schema statements in a Batch, one statement per execute (multi-statement strings separated by ; are not supported).
  • onConfigure is the place for PRAGMA foreign_keys = ON, db.setJournalMode('WAL') (extension SqfliteDatabaseExt, handles the Android quirk where execute fails), PRAGMA auto_vacuum (only when await db.getVersion() == 0, i.e. a new file) and, on Android, db.androidSetLocale('fr-FR') (extension SqfliteDatabaseAndroidExt). They must be re-applied at every open, which is why they belong there.
  • db.getVersion() / db.setVersion() exist (extension SqfliteDatabaseExecutorExt) but do not drive migrations with them; use version and the callbacks.
  • openDatabase(path, options: OpenDatabaseOptions(...)) is equivalent to the named parameters; when options is given all other parameters are ignored. DatabaseFactory.openDatabase only takes options.

Instances, closing, deleting

  • Open the database once and keep the Database; store the Future<Database> (not the Database) in a field so concurrent callers share the same open call. Many apps never close it.
  • singleInstance: true (default) returns the same Database for the same path; a second openDatabase on that path returns the existing instance and ignores its callbacks. It is forced to false for inMemoryDatabasePath (':memory:'). Opening the same file twice with singleInstance: false causes "database is locked" errors on Android.
  • rollbackActiveTransactionOnOpen (OpenDatabaseOptions, default: true in debug, false in release) rolls back a transaction left open by a previous isolate/hot restart when singleInstance is true. Keep the default unless you deliberately use several isolates.
  • readOnly: true (or openReadOnlyDatabase(path)) ignores every callback, never starts a transaction and fails on the first write. Use it for shipped asset databases.
  • Delete with deleteDatabase(path), never with File(path).delete(): it closes the open instance, handles the hot-restart state and removes the -wal, -shm and -journal side files.
  • databaseExists(path) checks the file; db.isOpen tells if close() was called; db.path is the resolved absolute path.
  • databaseFactory.readDatabaseBytes(path) / writeDatabaseBytes(path, bytes) copy a whole database file (backup, restore, asset import) in a way that also works with the ffi and web factories.
  • factory.sandbox(path: root) (extension SqfliteDatabaseFactorySandboxExtension) returns a DatabaseFactory whose relative paths live under root and whose absolute paths must stay inside it. Use it to isolate tests or per-user data.

Isolates and hot restart

  • Use the database from the main isolate: native calls already run on a background thread and the transaction lock is not cross-isolate. If a background isolate (push notification, work manager) must read the database, open it there with singleInstance: false and do not close it.
  • After changing the schema during development restart the app; a hot reload keeps the native connection open with the old schema.

Examples

Migration chain with batches and a downgrade policy

import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';

const _version = 2;

void _createV1(Batch batch) {
  batch.execute('DROP TABLE IF EXISTS Company');
  batch.execute(
    'CREATE TABLE Company (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)',
  );
}

void _upgradeV1ToV2(Batch batch) {
  batch.execute('ALTER TABLE Company ADD description TEXT');
  batch.execute('''CREATE TABLE Employee (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT,
    companyId INTEGER,
    FOREIGN KEY (companyId) REFERENCES Company(id) ON DELETE CASCADE)''');
}

Future<Database> openCompanyDb() async {
  final path = join(await getDatabasesPath(), 'company.db');
  return openDatabase(
    path,
    version: _version,
    onConfigure: (db) async {
      await db.execute('PRAGMA foreign_keys = ON');
    },
    onCreate: (db, version) async {
      // Fresh install: build the latest schema through the same steps.
      final batch = db.batch();
      _createV1(batch);
      _upgradeV1ToV2(batch);
      await batch.commit();
    },
    onUpgrade: (db, oldVersion, newVersion) async {
      final batch = db.batch();
      if (oldVersion < 2) {
        _upgradeV1ToV2(batch);
      }
      await batch.commit();
    },
    onDowngrade: onDatabaseDowngradeDelete,
  );
}

One shared instance for the whole app

import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';

class AppDatabase {
  Future<Database>? _db;

  /// Safe to call concurrently: the first call starts the open, others await it.
  Future<Database> get database => _db ??= _open();

  Future<Database> _open() async {
    final path = join(await getDatabasesPath(), 'app.db');
    return openDatabase(
      path,
      version: 1,
      onConfigure: (db) => db.setJournalMode('WAL'),
      onCreate: (db, _) => db.execute(
        'CREATE TABLE Note (id INTEGER PRIMARY KEY, content TEXT)',
      ),
    );
  }

  Future<void> close() async {
    final db = await _db;
    _db = null;
    await db?.close();
  }
}

Copy a bundled asset database on first launch

import 'dart:io';

import 'package:flutter/services.dart' show rootBundle;
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';

Future<Database> openAssetDatabase() async {
  final path = join(await getDatabasesPath(), 'catalog.db');
  if (!await databaseExists(path)) {
    await Directory(dirname(path)).create(recursive: true);
    final data = await rootBundle.load(url.join('assets', 'catalog.db'));
    await databaseFactory.writeDatabaseBytes(
      path,
      data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes),
    );
  }
  // Shipped data: open read-only, no callbacks run.
  return openReadOnlyDatabase(path);
}

Delete and recreate, in-memory database

import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';

Future<void> resetDatabase() async {
  final path = join(await getDatabasesPath(), 'app.db');
  await deleteDatabase(path); // also closes the open instance
}

Future<Database> openScratchDb() =>
    openDatabase(inMemoryDatabasePath); // singleInstance is forced to false

Opening through an explicit factory

import 'package:sqflite/sqflite.dart';

/// Works with databaseFactory (sqflite), databaseFactoryFfi, databaseFactoryFfiWeb...
Future<Database> openWith(DatabaseFactory factory, String path) {
  return factory.openDatabase(
    path,
    options: OpenDatabaseOptions(
      version: 1,
      onCreate: (db, version) =>
          db.execute('CREATE TABLE Test (id INTEGER PRIMARY KEY, value TEXT)'),
    ),
  );
}

Common mistakes

  • Calling db.transaction() inside onCreate / onUpgrade: they are already in a transaction, use db directly or a batch.
  • Creating two tables in one execute string. One statement per call.
  • Forgetting to bump version after changing onCreate; existing installs never see the new schema. Handle it in onUpgrade too.
  • Deleting the file with dart:io instead of deleteDatabase, then wondering why onCreate does not run after a hot restart.
  • Opening the database in every widget/repository call with singleInstance: false, leading to database is locked (code 5).
  • Putting PRAGMA foreign_keys / setJournalMode in onCreate: they are per connection and must go in onConfigure.
  • Calling openDatabase on Linux/Windows/web without first setting databaseFactory from sqflite_common_ffi / sqflite_common_ffi_web (StateError: databaseFactory not initialized).

More

Queries, transactions and batches: see the sqflite-crud-and-transactions skill. Unit tests, desktop/web factories and logging: see the sqflite-testing-and-platforms skill.

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-testing-and-platforms/SKILL.md

Metadata

Files
0
Version
aaabf90
Hash
7f355930
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