【前言】在 Flutter 跨平台开发中,本地数据持久化是核心需求之一,无论是存储用户配置、离线缓存还是业务数据,都需要可靠的本地存储方案。sqflite 作为 Flutter 生态中最主流的 SQLite 封装库,以其轻量、高效、跨平台的特性,成为本地结构化数据存储的首选。本文聚焦 sqflite 2.4.2 稳定版本,从核心特性解析、环境配置、基础操作、高级用法到问题排查,提供一套完整的落地方案,帮助开发者快速掌握本地数据库开发技巧。
📋 核心内容概览: 1. sqflite 2.4.2 核心特性与平台支持范围 2. 分步完成多平台环境配置(避坑指南) 3. 基础实战:数据库增删改查(CRUD)全流程 4. 高级用法:事务、批量操作与数据迁移 5. 类型适配、性能优化与最佳实践 6. 常见问题排查(FAQ)
Flutter 本地存储权威指南:sqflite 2.4.2 全平台集成与实战
引言
- 本地存储在现代移动应用开发中的重要性
- Flutter 生态系统中的本地存储解决方案概述
- sqflite 2.4.2 版本的新特性和改进
sqflite 2.4.2 基础概念
- 什么是 sqflite 及其在 Flutter 中的作用
- sqflite 2.4.2 版本的核心功能和性能优化
- 支持的平台和系统要求
环境配置与安装
- 添加 sqflite 2.4.2 到 Flutter 项目的步骤
- 必要的依赖项和兼容性检查
- 平台特定配置(Android/iOS/Web/Desktop)
数据库创建与管理
- 初始化数据库连接的最佳实践
- 数据库版本控制和升级策略
- 数据库关闭和资源释放
表结构与数据模型设计
- 定义表结构和字段类型
- 主键、索引和外键的使用
- 数据模型与 Dart 类的映射
CRUD 操作详解
- 插入数据的多种方法及性能考量
- 查询数据的条件筛选和排序
- 更新和删除数据的高效实现
- 批量操作和事务处理
高级查询与优化
- 复杂 SQL 查询的构建
- 多表连接和子查询的使用
- 查询性能优化技巧
数据类型与转换
- sqflite 支持的数据类型
- Dart 类型与 SQLite 类型的映射
- 自定义类型转换的实现
错误处理与调试
- 常见的错误类型和解决方案
- 调试数据库操作的工具和方法
- 日志记录和性能监控
多平台适配与兼容性
- 各平台(Android/iOS/Web/Desktop)的差异处理
- 平台特定问题的解决方案
- 跨平台一致性的保证
实战案例
- 简单的待办事项应用实现
- 用户数据持久化方案
- 离线优先策略的实现
性能优化与最佳实践
- 数据库操作的性能优化技巧
- 内存管理和资源利用
- 安全性和数据保护
测试策略
- 单元测试和集成测试的编写
- 模拟数据库环境进行测试
- 自动化测试的实践
常见问题解答
- 安装和配置中的常见问题
- 运行时错误的排查
- 社区支持和资源推荐
结论
- sqflite 2.4.2 在 Flutter 本地存储中的优势
- 未来发展趋势和可能的改进
- 鼓励读者实践和贡献

一、基础认知:sqflite 2.4.2 核心能力拆解
sqflite 是 Flutter 官方推荐的 SQLite 插件,2.4.2 版本作为稳定迭代版,在兼容性、性能和 API 易用性上均有优化,完美适配 Flutter 3.x 空安全生态,能够满足大多数本地结构化数据存储场景的需求。
1.1 核心特性亮点
-
全平台支持:原生支持 iOS、Android、macOS,通过 sqflite_common_ffi 扩展支持 Linux、Windows 及 DartVM,实验性支持 Web(sqflite_common_ffi_web),真正实现一次开发多端复用
-
完善的事务支持:支持数据库事务操作,确保一组操作的原子性,要么全部成功,要么全部回滚,避免数据不一致
-
批量操作优化:提供 Batch 批量操作 API,减少 Dart 与原生代码的交互次数,大幅提升大量数据操作的性能
-
自动版本管理:打开数据库时支持指定版本,通过 onCreate、onUpgrade、onDowngrade 回调自动处理 schema 变更,简化版本迭代中的数据迁移流程
-
便捷的 CRUD 辅助方法:封装了 insert、query、update、delete 等辅助方法,无需编写复杂的原生 SQL,同时也支持原生 SQL 语句,兼顾易用性与灵活性
-
后台线程执行:iOS 和 Android 平台上,数据库操作自动在后台线程执行,避免阻塞 UI 主线程,提升应用流畅度
-
空安全兼容:完全适配 Flutter 空安全规范,消除编译警告,提升代码健壮性
1.2 平台支持范围与限制
|----------------------|-------------------------------|-------------------------------------------------------------------|
| 平台 | 支持方式 | 核心限制与注意事项 |
| iOS | 原生支持 | 数据库文件默认存储在 Documents 目录,需注意 App 卸载后数据会丢失;需开启 Xcode Signing 配置 |
| Android | 原生支持 | 数据库文件默认存储在应用私有目录,其他应用无法访问;API 16+ 兼容,需注意权限配置(无需额外存储权限) |
| macOS | 原生支持 | 需开启沙盒权限,否则可能出现文件访问失败;数据库默认存储在应用容器目录 |
| Linux/Windows/DartVM | 扩展支持(sqflite_common_ffi) | 需额外添加 sqflite_common_ffi 依赖;Windows 需注意路径分隔符,Linux 需确保系统安装 SQLite |
| Web | 实验性支持(sqflite_common_ffi_web) | 功能不完善,不建议生产环境使用;基于 WebAssembly 实现,性能和兼容性有限 |

二、环境配置:多平台集成分步指南
sqflite 2.4.2 的集成需完成 Flutter 端依赖添加和对应平台的原生配置,不同平台的配置重点不同,以下是详细步骤。
2.1 第一步:添加依赖(Flutter 端)
打开项目根目录的 pubspec.yaml 文件,在 dependencies 节点下添加 sqflite 核心依赖,若需要支持 Linux/Windows 等桌面平台,需额外添加 sqflite_common_ffi 扩展依赖:
dependencies:
flutter:
sdk: flutter
# sqflite 核心依赖(指定 2.4.2 稳定版)
sqflite: 2.4.2
# 路径处理依赖(用于拼接数据库路径,推荐添加)
path: ^1.8.3
# 桌面端扩展依赖(Linux/Windows/DartVM 需添加)
dev_dependencies:
sqflite_common_ffi: ^2.3.0
添加完成后,执行以下命令安装依赖:
flutter pub get
2.2 第二步:各平台原生配置
2.2.1 iOS 配置(无需额外操作)
sqflite 2.4.2 对 iOS 平台的适配已高度自动化,无需手动添加权限或修改配置。仅需注意:
-
项目需开启 Signing 配置(Xcode 中选择项目 → Runner → Signing & Capabilities,勾选 Automatically manage signing)
-
测试设备需为 iOS 10.0+ 版本
2.2.2 Android 配置(无需额外操作)
Android 平台无需手动添加存储权限,因为 sqflite 操作的是应用私有目录下的文件,属于应用内部数据,不涉及外部存储访问。仅需注意:
-
最低支持 Android API 16+(Android 4.1+)
-
避免在 Android 11+ 平台直接操作外部存储路径,优先使用 sqflite 提供的默认路径
2.2.3 macOS 配置(开启沙盒权限)
macOS 平台需开启沙盒权限,否则会出现数据库文件访问失败的问题,步骤如下:
-
打开 macOS 项目(路径:
macos/Runner.xcworkspace) -
在 Xcode 中选择项目 → Runner → Signing & Capabilities
-
点击 + Capability,添加 App Sandbox capability
-
勾选 App Sandbox 下的 Allow user-selected file access(读取本地文件权限)
2.2.4 Linux/Windows 配置(扩展依赖集成)
Linux 和 Windows 平台需通过 sqflite_common_ffi 扩展实现支持,除了添加依赖外,还需在代码中初始化 FFI:
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
void main() {
// 初始化 FFI(Linux/Windows 平台必须添加)
sqfliteFfiInit();
// 指定数据库工厂
databaseFactory = databaseFactoryFfi;
runApp(const MyApp());
}
补充说明:
-
Windows 平台无需额外系统配置,支持 Windows 10+ 版本
-
Linux 平台需确保系统已安装 SQLite(Ubuntu 可通过
sudo apt-get install sqlite3安装)
三、基础实战:数据库 CRUD 全流程
本节以「待办事项(Todo)」为例,演示 sqflite 2.4.2 的核心用法,包括数据库创建、表结构定义、增删改查操作,代码可直接复制到项目中使用。
3.1 核心准备:定义数据模型与数据库工具类
首先定义 Todo 数据模型,封装 toMap(对象转 Map)和 fromMap(Map 转对象)方法,便于数据与数据库的交互;同时创建数据库工具类,统一管理数据库的打开、创建和关闭。
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
// 待办事项数据模型
class Todo {
final int? id; // 主键,自增
final String title; // 待办标题
final bool done; // 是否完成(0:未完成,1:已完成)
// 构造方法
Todo({
this.id,
required this.title,
this.done = false,
});
// 对象转 Map(用于插入/更新数据库)
Map<String, Object?> toMap() {
return {
'id': id,
'title': title,
'done': done ? 1 : 0, // SQLite 不支持 bool 类型,用 0/1 表示
};
}
// Map 转对象(用于从数据库查询数据)
static Todo fromMap(Map<String, Object?> map) {
return Todo(
id: map['id'] as int?,
title: map['title'] as String,
done: (map['done'] as int) == 1,
);
}
// 重写 toString 方法,便于调试
@override
String toString() {
return 'Todo{id: $id, title: $title, done: $done}';
}
}
// 数据库工具类(单例模式,确保全局唯一数据库实例)
class TodoDatabase {
// 单例实例
static final TodoDatabase instance = TodoDatabase._init();
// 数据库对象
static Database? _database;
// 私有构造方法,防止外部实例化
TodoDatabase._init();
// 获取数据库实例(懒加载)
Future<Database> get database async {
if (_database != null) return _database!;
// 初始化数据库
_database = await _initDB('todos.db');
return _database!;
}
// 初始化数据库(创建数据库文件和表)
Future<Database> _initDB(String filePath) async {
// 获取数据库默认存储路径
final dbPath = await getDatabasesPath();
// 拼接完整的数据库路径
final path = join(dbPath, filePath);
// 打开数据库,指定版本和创建回调
return await openDatabase(
path,
version: 1,
onCreate: _createDB, // 数据库首次创建时执行
);
}
// 创建数据库表结构
Future<void> _createDB(Database db, int version) async {
// 创建 todo 表
await db.execute('''
CREATE TABLE todos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
done INTEGER NOT NULL DEFAULT 0
)
''');
}
// 关闭数据库
Future<void> close() async {
final db = await instance.database;
db.close();
_database = null;
}
}
3.2 实战 1:插入数据(Create)
通过 insert 辅助方法插入 Todo 数据,支持单条数据插入,插入成功后返回自增的主键 ID。
// 插入单条待办事项
Future<Todo> insertTodo(Todo todo) async {
final db = await TodoDatabase.instance.database;
// 调用 insert 方法,返回自增 ID
final id = await db.insert('todos', todo.toMap());
// 返回包含自增 ID 的 Todo 对象
return todo.copyWith(id: id);
}
// 使用示例
void testInsert() async {
final todo = Todo(title: '学习 sqflite 2.4.2');
final insertedTodo = await insertTodo(todo);
print('插入成功:$insertedTodo');
}
3.3 实战 2:查询数据(Read)
支持单条数据查询和批量查询,通过 query 辅助方法可灵活设置查询条件、排序方式等,查询结果为 Map 列表,需转换为 Todo 对象。
// 根据 ID 查询单条待办事项
Future<Todo?> getTodoById(int id) async {
final db = await TodoDatabase.instance.database;
// 查询条件:id = ?
final maps = await db.query(
'todos',
columns: ['id', 'title', 'done'],
where: 'id = ?',
whereArgs: [id], // 避免 SQL 注入,优先使用 whereArgs
);
if (maps.isNotEmpty) {
return Todo.fromMap(maps.first);
} else {
return null; // 未查询到数据
}
}
// 查询所有待办事项(支持排序)
Future<List<Todo>> getAllTodos({String? orderBy}) async {
final db = await TodoDatabase.instance.database;
// 查询所有数据,可选排序方式(如 'id DESC' 按 ID 倒序)
final maps = await db.query(
'todos',
orderBy: orderBy ?? 'id ASC',
);
// 将 Map 列表转换为 Todo 列表
return maps.map((map) => Todo.fromMap(map)).toList();
}
// 查询已完成/未完成的待办事项(条件查询)
Future<List<Todo>> getTodosByStatus(bool done) async {
final db = await TodoDatabase.instance.database;
final maps = await db.query(
'todos',
where: 'done = ?',
whereArgs: [done ? 1 : 0],
);
return maps.map((map) => Todo.fromMap(map)).toList();
}
// 使用示例
void testQuery() async {
// 查询所有待办
final allTodos = await getAllTodos();
print('所有待办:$allTodos');
// 根据 ID 查询
final todo = await getTodoById(1);
print('ID 为 1 的待办:$todo');
// 查询已完成的待办
final doneTodos = await getTodosByStatus(true);
print('已完成的待办:$doneTodos');
}
注意:查询结果返回的 Map 是只读的,若需要修改内存中的数据,需创建新的 Map 副本(如
Map<String, Object?> newMap = Map.from(readOnlyMap)),否则会抛出异常。
3.4 实战 3:更新数据(Update)
通过 update 辅助方法更新数据,需指定更新条件(如 ID),避免全表更新;更新成功后返回受影响的行数。
// 更新待办事项(根据 ID 更新)
Future<int> updateTodo(Todo todo) async {
final db = await TodoDatabase.instance.database;
// 更新条件:id = ?
return await db.update(
'todos',
todo.toMap(),
where: 'id = ?',
whereArgs: [todo.id],
);
}
// 使用示例
void testUpdate() async {
// 先查询一条数据
final todo = await getTodoById(1);
if (todo != null) {
// 修改待办状态为已完成
final updatedTodo = todo.copyWith(done: true);
// 执行更新
final affectedRows = await updateTodo(updatedTodo);
print('更新成功,受影响行数:$affectedRows');
}
}
3.5 实战 4:删除数据(Delete)
通过 delete 辅助方法删除数据,支持根据条件删除单条或多条数据,删除成功后返回受影响的行数。
// 根据 ID 删除单条待办事项
Future<int> deleteTodo(int id) async {
final db = await TodoDatabase.instance.database;
return await db.delete(
'todos',
where: 'id = ?',
whereArgs: [id],
);
}
// 删除所有已完成的待办事项
Future<int> deleteDoneTodos() async {
final db = await TodoDatabase.instance.database;
return await db.delete(
'todos',
where: 'done = ?',
whereArgs: [1],
);
}
// 使用示例
void testDelete() async {
// 根据 ID 删除
final deletedRows = await deleteTodo(1);
print('删除成功,受影响行数:$deletedRows');
// 删除所有已完成的待办
final deletedDoneRows = await deleteDoneTodos();
print('删除已完成待办,受影响行数:$deletedDoneRows');
}
四、高级用法:事务、批量操作与数据迁移
sqflite 2.4.2 提供了事务、批量操作等高级特性,可满足复杂业务场景的需求;同时支持自动版本管理,简化数据迁移流程。
4.1 事务操作(确保原子性)
事务适用于一组需要同时成功或同时失败的操作(如转账场景:扣款和到账必须同时完成)。sqflite 中通过 transaction 方法实现事务,回调中使用 txn 对象操作数据库,若回调抛出异常,事务会自动回滚。
// 事务示例:同时插入两条待办,确保要么都成功,要么都失败
Future<void> insertTwoTodosInTransaction() async {
final db = await TodoDatabase.instance.database;
try {
await db.transaction((txn) async {
// 事务中必须使用 txn 对象操作数据库,不能使用 db 对象(否则会死锁)
await txn.insert('todos', Todo(title: '事务测试1').toMap());
await txn.insert('todos', Todo(title: '事务测试2').toMap());
// 若需要回滚,可主动抛出异常
// throw StateError('主动回滚事务');
});
print('事务执行成功,两条数据插入完成');
} catch (e) {
print('事务执行失败,数据回滚:$e');
}
}
警告:事务回调中必须使用 txn 对象操作数据库,禁止使用 db 对象,否则会导致死锁;onCreate、onUpgrade、onDowngrade 回调内部已默认包裹事务,无需手动添加。
4.2 批量操作(提升大量数据处理性能)
当需要批量插入、更新或删除大量数据时,使用 Batch 批量操作可减少 Dart 与原生代码的交互次数,大幅提升性能。批量操作的结果可通过 commit 方法获取,也可通过 noResult 参数关闭结果返回以进一步优化性能。
// 批量插入待办事项
Future<void> batchInsertTodos(List<Todo> todos) async {
final db = await TodoDatabase.instance.database;
// 创建批量操作对象
final batch = db.batch();
// 添加批量插入操作
for (final todo in todos) {
batch.insert('todos', todo.toMap());
}
// 执行批量操作(noResult: true 表示不返回结果,性能更好)
// 若需要获取结果,可省略 noResult 参数,结果为 List<dynamic>
await batch.commit(noResult: true);
print('批量插入 ${todos.length} 条数据完成');
}
// 批量更新待办状态
Future<void> batchUpdateTodosStatus(List<int> ids, bool done) async {
final db = await TodoDatabase.instance.database;
final batch = db.batch();
for (final id in ids) {
batch.update(
'todos',
{'done': done ? 1 : 0},
where: 'id = ?',
whereArgs: [id],
);
}
await batch.commit(noResult: true);
print('批量更新 ${ids.length} 条数据完成');
}
// 使用示例
void testBatchOperation() async {
// 批量插入 10 条待办
final todos = List.generate(10, (index) => Todo(title: '批量测试 $index'));
await batchInsertTodos(todos);
// 批量更新前 5 条待办为已完成
final ids = List.generate(5, (index) => index + 1);
await batchUpdateTodosStatus(ids, true);
}
补充说明:
-
批量操作在事务中执行时,需使用 txn.batch() 创建批量对象,且批量操作的提交会延迟到事务提交时执行
-
通过 continueOnError: true 参数可设置批量操作忽略错误,即使某条操作失败,其他操作仍会继续执行(默认会停止在错误处)
4.3 数据迁移(版本更新时的 schema 变更)
当应用迭代需要修改表结构(如新增字段、修改字段类型、新增表)时,可通过 sqflite 的版本管理机制实现自动数据迁移,核心是通过 onUpgrade 回调处理版本升级逻辑。
// 数据迁移示例:从版本 1 升级到版本 2(新增分类字段 category)
Future<Database> _initDB(String filePath) async {
final dbPath = await getDatabasesPath();
final path = join(dbPath, filePath);
return await openDatabase(
path,
version: 2, // 版本从 1 提升到 2
onCreate: _createDB,
onUpgrade: _onUpgrade, // 版本升级时执行
);
}
// 版本升级回调(oldVersion:旧版本号,newVersion:新版本号)
Future<void> _onUpgrade(Database db, int oldVersion, int newVersion) async {
if (oldVersion < 2) {
// 从版本 1 升级到 2,新增 category 字段(允许为空,默认值为 '默认分类')
await db.execute('''
ALTER TABLE todos ADD COLUMN category TEXT DEFAULT '默认分类'
''');
print('数据库从版本 $oldVersion 升级到 $newVersion,新增 category 字段');
}
}
// 补充:版本降级回调(可选,用于处理版本回退场景)
Future<void> _onDowngrade(Database db, int oldVersion, int newVersion) async {
if (oldVersion > 2 && newVersion == 1) {
// 从版本 2 降级到 1,删除 category 字段(注意:删除字段会丢失数据)
await db.execute('''
ALTER TABLE todos DROP COLUMN category
''');
print('数据库从版本 $oldVersion 降级到 $newVersion,删除 category 字段');
}
}
数据迁移最佳实践:
-
版本号必须递增,不允许跳跃(如从 1 直接升级到 3,需依次处理 1→2、2→3 的升级逻辑)
-
ALTER TABLE 仅支持新增字段,不支持修改字段类型或删除字段(如需修改,需创建临时表、迁移数据、删除旧表、重命名临时表)
-
升级过程中建议备份关键数据,避免数据丢失
五、类型适配、性能优化与最佳实践
sqflite 基于 SQLite,而 SQLite 支持的类型与 Dart 类型存在差异,同时合理的性能优化可提升应用体验,以下是关键要点。
5.1 类型适配指南(SQLite ↔ Dart)
SQLite 支持的类型有限(INTEGER、REAL、TEXT、BLOB),与 Dart 类型的适配需注意以下几点:
|-----------|-----------------|---------------------------------------------------------------------|
| SQLite 类型 | Dart 类型 | 注意事项 |
| INTEGER | int | 支持范围:-2^63 到 2^63 - 1;Dart bool 类型需转换为 0/1 存储 |
| REAL | num(int/double) | 用于存储浮点数,注意精度问题 |
| TEXT | String | 支持 UTF-8 编码字符串;DateTime 可转换为 ISO8601 字符串(如 '2025-12-18T12:00:00')存储 |
| BLOB | Uint8List | 用于存储二进制数据(如图片、文件),建议仅存储小体积二进制数据,大文件优先使用文件系统 |
示例:DateTime 类型的存储与读取
// 存储:将 DateTime 转换为 ISO8601 字符串
String dateStr = DateTime.now().toIso8601String();
await db.insert('todos', {'title': '测试时间', 'date': dateStr});
// 读取:将字符串转换为 DateTime
final maps = await db.query('todos', where: 'title = ?', whereArgs: ['测试时间']);
if (maps.isNotEmpty) {
String dateStr = maps.first['date'] as String;
DateTime date = DateTime.parse(dateStr);
print('时间:$date');
}
5.2 性能优化建议
-
使用批量操作处理大量数据:避免循环调用单条 insert/update/delete,优先使用 Batch 批量操作,减少跨线程交互开销
-
合理使用索引 :对于频繁查询的字段(如 where 条件中常用的字段),创建索引可提升查询性能(如
CREATE INDEX idx_todos_title ON todos(title));但索引会增加插入/更新的开销,需权衡使用 -
减少查询字段 :query 方法中通过 columns 参数指定需要查询的字段,避免使用
SELECT *查询所有字段,尤其当表中有 BLOB 字段时 -
缓存数据库实例:采用单例模式管理数据库实例,避免频繁打开和关闭数据库(打开数据库是耗时操作)
-
避免在 UI 主线程执行复杂查询:虽然 sqflite 操作在后台线程执行,但复杂查询的结果处理可能阻塞 UI,建议使用 compute 函数将结果处理放在单独的 isolate 中
-
使用 LIMIT 分页查询 :对于大量数据的列表展示,采用分页查询(如
LIMIT 20 OFFSET 0),避免一次性加载所有数据导致内存占用过高
5.3 最佳实践
-
表名和字段名避免使用 SQLite 关键字 :如 'table'、'group'、'order' 等,若必须使用,需用双引号包裹(如
db.query('"table"')) -
使用参数化查询避免 SQL 注入 :优先使用 whereArgs 传递查询参数,避免直接拼接 SQL 字符串(如
where: 'id = ?', whereArgs: [id]) -
及时关闭数据库:在应用退出或不再使用数据库时,调用 close 方法关闭数据库,释放资源;但无需在每次操作后关闭(频繁关闭/打开会降低性能)
-
封装数据库操作层:将数据库操作封装在单独的工具类或仓库层(Repository)中,与 UI 层解耦,便于维护和测试
-
测试数据库操作:使用 sqflite_common_ffi 在 DartVM 中测试数据库操作,无需依赖 Flutter 环境,提升测试效率
六、常见问题排查(FAQ)
汇总 sqflite 2.4.2 开发中高频问题及解决方案,帮助快速定位问题。
Q1:Android 端数据库操作失败,提示「database not open」?
A1:原因可能是数据库未初始化完成就执行操作,或数据库已被关闭。解决方案:
-
确保所有数据库操作都在获取数据库实例(await TodoDatabase.instance.database)之后执行
-
检查是否存在误关闭数据库的逻辑,避免在操作过程中关闭数据库
Q2:iOS 端模拟器测试正常,真机测试提示「文件不存在」?
A2:大概率是 Signing 配置问题。解决方案:
-
在 Xcode 中检查项目的 Signing 配置,确保已登录 Apple Developer 账号,且 Team 选择正确
-
清洁项目(Product → Clean Build Folder)后重新构建
Q3:macOS 端提示「Sandbox access violation」?
A3:未开启沙盒权限。解决方案:在 Xcode 中添加 App Sandbox capability,并勾选 Allow user-selected file access(参考 2.2.3 节配置)。
Q4:查询结果的 Map 无法修改,抛出「Unsupported operation: read-only」?
A4:sqflite 返回的查询结果 Map 是只读的。解决方案:创建 Map 副本后再修改(如 Map<String, Object?> newMap = Map.from(readOnlyMap))。
Q5:事务中操作数据库死锁?
A5:事务回调中使用了 db 对象而非 txn 对象。解决方案:事务中所有数据库操作必须通过 txn 对象执行(如 txn.insert(...)、txn.query(...))。
Q6:Windows/Linux 端提示「database factory not initialized」?
A6:未初始化 sqflite_common_ffi。解决方案:在 main 函数中添加 FFI 初始化代码(参考 2.2.4 节配置)。
Q7:新增字段后,查询时提示「no such column」?
A7:未处理数据迁移,数据库版本未提升。解决方案:将数据库版本号递增,在 onUpgrade 回调中添加 ALTER TABLE 语句新增字段(参考 4.3 节)。
七、总结
sqflite 2.4.2 作为 Flutter 本地结构化数据存储的主流方案,以其全平台支持、完善的 API 和高效的性能,能够满足大多数应用的本地存储需求。通过本文的指南,从环境配置、基础 CRUD 到高级的事务、批量操作和数据迁移,再到性能优化和问题排查,开发者可系统掌握 sqflite 的使用技巧。
在实际开发中,建议结合业务场景合理设计表结构,封装数据库操作层,遵循最佳实践确保数据安全和应用性能。对于复杂的本地存储需求,还可结合其他方案(如 Hive 用于键值对存储、Drift 用于更复杂的 SQL 操作),构建更完善的本地存储体系。
扩展资源:
-
sqflite 官方文档:https://pub.dev/packages/sqflite
-
sqflite_common_ffi 文档:https://pub.dev/packages/sqflite_common_ffi
-
SQLite 官方文档:https://www.sqlite.org/docs.html
-
示例项目:notepad_sqflite(支持全平台的记事本应用)