You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Flutter应用预装SQLite预置数据及版本更新数据适配的最佳实践咨询

嘿,作为经常折腾Flutter本地数据库的开发者,我来给你捋清楚这个问题的最佳实践——不管是首次安装预置默认数据,还是后续更新时处理新旧用户的数据,核心都是靠sqflite的版本控制+合理的资源复制逻辑,下面一步步给你拆解:

一、首次安装:给应用预装默认SQLite数据

最稳妥的方式是提前在本地建好带默认数据的数据库文件,然后在应用首次启动时复制到沙盒目录,具体步骤如下:

  1. 准备预置数据库文件
    用SQLite可视化工具(比如DB Browser for SQLite)创建一个.db文件,把你需要的默认数据(比如分类、基础配置)提前插入好。然后把这个文件放到项目的assets/db/目录下,记得在pubspec.yaml里声明资源路径:

    flutter:
      assets:
        - assets/db/default_data.db
    
  2. 首次启动时复制数据库到应用沙盒
    应用的沙盒目录是唯一能持久化存储的地方,咱们用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

火山引擎 最新活动