Kodokon kodokon.com

用 shared_preferences 实现本地持久化

用 shared_preferences 持久化偏好设置和轻量数据,同时了解它在安全性和容量上的局限。

8 分钟 · 3 题

在 Kodokon 中打开本课

shared_preferences 是一个键/值存储,背后依托的是原生机制(Android 上的 SharedPreferences、iOS 上的 UserDefaults)。它的最佳场景是:小块数据 - 选定的主题、是否看过引导页、上次激活的标签页。超出这个范围就该换工具了:大块或需要查询的数据用 sqflitedrift 数据库,任何敏感的东西用 flutter_secure_storage。这个文件会被整个加载进内存:它既没有加密,也不是为增长而设计的。

BASH
flutter pub add shared_preferences

getInstance() 在第一次调用时是异步的(要读取文件),之后会返回一个被缓存的实例:随后的 get* 调用是同步的,直接从内存中取值。而 set* 调用则会异步地写入磁盘。在实践中,不要把键的字符串散落在整个应用各处:把它们集中到一个类型化的、可注入的、可测试的封装里。

DART
import 'package:shared_preferences/shared_preferences.dart';

class SettingsStore {
  SettingsStore(this._prefs);

  static const _darkModeKey = 'settings.darkMode';

  final SharedPreferences _prefs;

  static Future<SettingsStore> load() async {
    final prefs = await SharedPreferences.getInstance();
    return SettingsStore(prefs);
  }

  bool get darkMode =>
      _prefs.getBool(_darkModeKey) ?? false;

  Future<void> setDarkMode(bool value) =>
      _prefs.setBool(_darkModeKey, value);
}
集中管理的键、有保障的类型、可注入的依赖

这个 API 只存储原始类型:boolintdoubleString 以及 List<String>。对于一个结构化对象,请把它序列化成 JSON - 你在第一课里写的 toJson/fromJson 在这里又能派上用场了。一个可以接受的取舍是:每次保存都会重写整个值,而且无法进行查询。它非常适合保存一份草稿,却不适合保存一段历史记录。

DART
import 'dart:convert';

import 'package:shared_preferences/shared_preferences.dart';

class Draft {
  const Draft({required this.title, required this.body});

  factory Draft.fromJson(Map<String, dynamic> json) =>
      Draft(
        title: json['title'] as String,
        body: json['body'] as String,
      );

  final String title;
  final String body;

  Map<String, dynamic> toJson() =>
      {'title': title, 'body': body};
}

Future<void> saveDraft(Draft draft) async {
  final prefs = await SharedPreferences.getInstance();
  await prefs.setString('draft', jsonEncode(draft));
}

Future<Draft?> loadDraft() async {
  final prefs = await SharedPreferences.getInstance();
  final raw = prefs.getString('draft');
  if (raw == null) return null;
  return Draft.fromJson(
    jsonDecode(raw) as Map<String, dynamic>,
  );
}
一个完整的对象,以一个简单的 JSON 字符串形式存储

知识检测

确认你已牢记本课的重点内容。

  1. 哪种数据适合放进 shared_preferences
    • 用户的身份验证令牌。
    • 用户包含 50,000 条交易的完整历史记录。
    • 深色主题的选择。
  2. 在第一次 getInstance() 之后,get* 读取操作的表现如何?
    • 每次读取都会从磁盘重新读一遍文件。
    • 它们是同步的:值直接从内存缓存中取出。
    • 它们返回 Future,每次都必须 await。
  3. 如何保存一个结构化的 Draft 对象?
    • jsonEncode 把它序列化成 JSON,再通过 setString 存储。
    • 使用为此专门设计的 prefs.setObject('draft', draft)
    • 把它逐字段拆开,存进一个 setStringList