背景
Flutter 应用只要不是纯展示页面,基本都会遇到本地数据持久化的问题。
比如:
- 保存用户登录状态
- 保存主题、语言、开关配置
- 缓存接口返回的数据
- 保存聊天记录、账单、笔记等业务数据
- 离线时继续使用,联网后再同步
这些场景看起来都叫“保存到本地”,但实际适合的方案不一样。如果一开始选错了,后面很容易出现数据结构难维护、查询困难、迁移麻烦的问题。
常见方案
Flutter 里常见的本地持久化方案大概有这几类:
| 方案 | 适合场景 | 不适合场景 |
|---|---|---|
| shared_preferences | 少量配置、开关、token | 复杂业务数据 |
| 文件存储 | 日志、缓存、导入导出文件 | 频繁查询和更新 |
| SQLite | 结构化数据、本地业务表 | 简单配置项 |
| Drift | 类型安全的 SQLite 封装 | 很小的 demo |
| Hive/Isar | 本地对象存储、轻量缓存 | 强关系型查询 |
我的习惯是先按照“数据复杂度”和“查询需求”来选。
shared_preferences
shared_preferences 适合保存少量键值数据,比如主题、语言、是否首次启动。
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('is_dark_mode', true);await prefs.setString('token', 'xxx');
final isDarkMode = prefs.getBool('is_dark_mode') ?? false;final token = prefs.getString('token');它的优点是简单,缺点也很明显:不适合存复杂对象,更不适合存列表型业务数据。
比较适合保存:
theme_modelocaleaccess_tokenfirst_launchlast_login_user_id
不建议保存:
- 聊天消息
- 账单明细
- 商品列表
- 复杂用户资料
文件存储
文件存储适合保存一些不需要复杂查询的内容,比如日志、图片缓存、导入导出的 JSON。
final directory = await getApplicationDocumentsDirectory();final file = File('${directory.path}/notes.json');
await file.writeAsString(jsonEncode([ {'title': 'Flutter', 'content': 'local storage'},]));
final content = await file.readAsString();final data = jsonDecode(content);这种方式直观,但问题是查询、更新、删除都要自己处理。数据少的时候没问题,数据一多就会变得很难维护。
如果数据本身天然就是文件,比如用户导出的备份、图片、日志,那文件存储很合适。如果是业务表数据,还是应该考虑数据库。
SQLite
SQLite 适合保存结构化业务数据。
比如一个记账应用,账单记录就很适合放到 SQLite:
CREATE TABLE ledger_entries ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, amount REAL NOT NULL, type TEXT NOT NULL, created_at INTEGER NOT NULL);它的优点是:
- 支持条件查询
- 支持排序、分页
- 支持事务
- 支持索引
- 数据结构清晰
缺点是直接写 SQL 会比较繁琐,字段改动后迁移也要自己处理。
Drift
如果项目里本地数据比较重要,我更倾向于用 Drift。它本质上还是 SQLite,但提供了更好的类型安全和 Dart 层封装。
表定义大概是这样:
class LedgerEntries extends Table { IntColumn get id => integer().autoIncrement()(); TextColumn get title => text()(); RealColumn get amount => real()(); TextColumn get type => text()(); DateTimeColumn get createdAt => dateTime()();}查询可以写成:
Future<List<LedgerEntry>> getEntries() { return (select(ledgerEntries) ..orderBy([ (table) => OrderingTerm.desc(table.createdAt), ])) .get();}插入数据:
Future<int> insertEntry(LedgerEntriesCompanion entry) { return into(ledgerEntries).insert(entry);}Drift 的好处是数据层更清楚,适合业务长期迭代。缺点是上手成本比 shared_preferences 高,也需要配置代码生成。
怎么选
可以简单按这个规则判断:
| 需求 | 推荐方案 |
|---|---|
| 保存几个配置项 | shared_preferences |
| 保存 token、主题、语言 | shared_preferences |
| 保存日志或导出文件 | 文件存储 |
| 保存列表数据 | SQLite 或 Drift |
| 需要条件查询、分页、排序 | SQLite 或 Drift |
| 项目会长期维护 | Drift |
| 只是快速 demo | shared_preferences 或简单文件 |
还有一个比较实用的判断方法:如果你开始想给数据加 id、createdAt、updatedAt,并且需要按条件查询,那基本就该用数据库了。
分层建议
不要在页面里直接操作数据库。比较清晰的结构是:
presentation 页面和状态domain 实体和业务接口data 本地数据库、接口实现页面只调用 repository:
final entries = await ledgerRepository.getEntries();具体数据来自 SQLite、接口还是缓存,页面不用关心。
这样后面从本地数据切到服务端同步,也不会大面积改 UI。
小结
Flutter 本地持久化不要只看“哪个库简单”,要先看数据类型。
我的选择习惯是:
- 少量配置用
shared_preferences - 文件内容用文件存储
- 业务数据用 SQLite
- 长期维护的业务数据用 Drift
如果只是保存一个开关,用数据库会显得太重;如果已经是账单、聊天记录、笔记这类数据,还继续用 JSON 文件或键值存储,后面维护成本会越来越高。