Flutter应用预装SQLite预置数据及版本更新数据适配的最佳实践咨询
嘿,作为经常折腾Flutter本地数据库的开发者,我来给你捋清楚这个问题的最佳实践——不管是首次安装预置默认数据,还是后续更新时处理新旧用户的数据,核心都是靠sqflite的版本控制+合理的资源复制逻辑,下面一步步给你拆解:
一、首次安装:给应用预装默认SQLite数据
最稳妥的方式是提前在本地建好带默认数据的数据库文件,然后在应用首次启动时复制到沙盒目录,具体步骤如下:
准备预置数据库文件
用SQLite可视化工具(比如DB Browser for SQLite)创建一个.db文件,把你需要的默认数据(比如分类、基础配置)提前插入好。然后把这个文件放到项目的assets/db/目录下,记得在pubspec.yaml里声明资源路径:flutter: assets: - assets/db/default_data.db首次启动时复制数据库到应用沙盒
应用的沙盒目录是唯一能持久化存储的地方,咱们用path_provider获取这个路径,然后检查数据库是否已经存在——如果不存在,就从assets里复制过去:import 'dart:io'; import 'package:flutter/services.dart'; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; import 'package:sqflite/sqflite.dart'; Future<Database> _initDB() async { final docDir = await getApplicationDocumentsDirectory(); final dbPath = join(docDir.path, "default_data.db"); // 检查数据库是否已存在,避免重复复制 if (!await File(dbPath).exists()) { // 从assets读取数据库文件并写入沙盒 final byteData = await rootBundle.load("assets/db/default_data.db"); final buffer = byteData.buffer; await File(dbPath).writeAsBytes( buffer.asUint8List(byteData.offsetInBytes, byteData.lengthInBytes) ); } // 打开数据库,这里先设置初始版本号 return await openDatabase(dbPath, version: 1); }
二、应用更新:区分新旧用户的数据处理
这里的核心是利用sqflite的数据库版本号和onUpgrade回调,每次更新默认数据或表结构时,递增版本号,让已安装用户触发迁移逻辑,首次安装用户直接用最新数据:
核心思路
- 首次安装用户:本地没有数据库,直接复制最新的预置文件,版本号是最新的,不会触发迁移。
- 已安装用户:打开数据库时,若本地版本号低于新的版本号,会自动调用
onUpgrade,咱们在这个回调里写对应版本的更新逻辑。
场景1:新增/更新部分默认数据
比如旧版本数据库是v1,新版本要加几个默认分类,同时避免重复插入:
// 把版本号改成2 Future<Database> _initDB() async { // ... 前面的复制逻辑不变 return await openDatabase( dbPath, version: 2, onUpgrade: _onUpgrade // 添加升级回调 ); } // 升级逻辑:根据旧版本号执行对应操作 Future<void> _onUpgrade(Database db, int oldVersion, int newVersion) async { if (oldVersion < 2) { // 用INSERT OR IGNORE避免重复插入已存在的数据 await db.execute(''' INSERT OR IGNORE INTO categories (id, name, is_default) VALUES (3, "新增分类1", 1), (4, "新增分类2", 1) '''); } }
场景2:修改表结构+更新数据
如果需要给现有表加字段,同时更新默认数据:
Future<void> _onUpgrade(Database db, int oldVersion, int newVersion) async { if (oldVersion < 2) { // 先给表加字段 await db.execute('ALTER TABLE products ADD COLUMN description TEXT'); // 再更新默认数据的描述字段 await db.execute(''' UPDATE products SET description = "默认描述" WHERE is_default = 1 '''); } }
场景3:完全替换默认数据(谨慎使用)
如果需要彻底替换默认数据,但要保留用户自己添加的数据,建议给默认数据加一个标识字段(比如is_default = 1),更新时先删除旧的默认数据,再插入新的:
Future<void> _onUpgrade(Database db, int oldVersion, int newVersion) async { if (oldVersion < 2) { // 删除旧的默认数据 await db.execute('DELETE FROM categories WHERE is_default = 1'); // 插入新的默认数据 await db.execute(''' INSERT INTO categories (id, name, is_default) VALUES (1, "新版分类1", 1), (2, "新版分类2", 1) '''); } }
三、注意事项
- 每次更新数据或表结构时,一定要递增数据库版本号,否则不会触发
onUpgrade。 - 预置数据库文件的版本号要和代码里的版本号保持一致,避免首次安装就触发不必要的迁移。
- 迁移逻辑要健壮:用
INSERT OR IGNORE、事务包裹操作,避免更新过程中出错导致数据损坏。
内容的提问来源于stack exchange,提问作者user2609021




