Skip to main content

sqflite-open-database

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.

الانتقال إلى التثبيت

معلومات المصدر

المستودع
tekartik/sqflite
آخر نشاط في المصدر
١٠ سبتمبر ٢٠٢٦ في ١٣:١٥
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
٣٬٠١٩
التفرعات
٥٥٥

خيارات التثبيت

يُحدَّد Prompt الذي يراجع المصدر أولًا بشكل افتراضي. يمكنك التبديل إلى أمر مباشر أو تنزيل نسخة محلية.

مراجعة ملفات المصدر

اقرأ SKILL.md وأي ملفات مرافقة يعرضها SkillsMP قبل أن تقرر التثبيت.

عرض SKILL.md

SKILL.md
تعليمات المصدر · معاينة للقراءة فقط
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. ```dart 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 ```dart 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 ```dart 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 ```dart 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 ```dart 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 ```dart 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.
عرض على GitHub