跳转到正文
YGJoon 技术博客

Flutter SharedPreferences 详解

发布于:
·1.3k 字 · 约 3 分钟·作者:
键值对小数据就用它:setString/getString 存取、remove 删除,包一层 PreferencesUtil 工具类,全局到处直接调
Flutter SharedPreferences 详解文章封面
目录

简介

SharedPreferences 是一个轻量级的数据持久化方案,适合存储少量的键值对数据,如用户设置、缓存等。

基本配置

添加依赖

dependencies:
  shared_preferences: ^2.2.0

基本使用

// 获取实例
final prefs = await SharedPreferences.getInstance();

// 存储数据
await prefs.setString('name', 'John');
await prefs.setInt('age', 25);
await prefs.setBool('isLogin', true);
await prefs.setStringList('tags', ['flutter', 'dart']);

// 读取数据
final name = prefs.getString('name') ?? 'Unknown';
final age = prefs.getInt('age') ?? 0;
final isLogin = prefs.getBool('isLogin') ?? false;
final tags = prefs.getStringList('tags') ?? [];

// 删除数据
await prefs.remove('name');

// 清空所有数据
await prefs.clear();

封装工具类

class PreferencesUtil {
  static SharedPreferences? _prefs;
  
  static Future<void> init() async {
    _prefs = await SharedPreferences.getInstance();
  }
  
  // 存储方法
  static Future<bool> setString(String key, String value) async {
    return await _prefs?.setString(key, value) ?? false;
  }
  
  static Future<bool> setInt(String key, int value) async {
    return await _prefs?.setInt(key, value) ?? false;
  }
  
  static Future<bool> setBool(String key, bool value) async {
    return await _prefs?.setBool(key, value) ?? false;
  }
  
  static Future<bool> setStringList(String key, List<String> value) async {
    return await _prefs?.setStringList(key, value) ?? false;
  }
  
  // 读取方法
  static String getString(String key, {String defaultValue = ''}) {
    return _prefs?.getString(key) ?? defaultValue;
  }
  
  static int getInt(String key, {int defaultValue = 0}) {
    return _prefs?.getInt(key) ?? defaultValue;
  }
  
  static bool getBool(String key, {bool defaultValue = false}) {
    return _prefs?.getBool(key) ?? defaultValue;
  }
  
  static List<String> getStringList(String key, {List<String> defaultValue = const []}) {
    return _prefs?.getStringList(key) ?? defaultValue;
  }
  
  // 删除方法
  static Future<bool> remove(String key) async {
    return await _prefs?.remove(key) ?? false;
  }
  
  static Future<bool> clear() async {
    return await _prefs?.clear() ?? false;
  }
  
  // 检查是否存在
  static bool containsKey(String key) {
    return _prefs?.containsKey(key) ?? false;
  }
}

完整示例

// 用户设置管理
class UserSettings {
  static const String keyThemeMode = 'theme_mode';
  static const String keyLanguage = 'language';
  static const String keyFontSize = 'font_size';
  static const String keyNotifications = 'notifications';
  
  // 主题设置
  static Future<void> setThemeMode(String mode) async {
    await PreferencesUtil.setString(keyThemeMode, mode);
  }
  
  static String getThemeMode() {
    return PreferencesUtil.getString(keyThemeMode, defaultValue: 'system');
  }
  
  // 语言设置
  static Future<void> setLanguage(String language) async {
    await PreferencesUtil.setString(keyLanguage, language);
  }
  
  static String getLanguage() {
    return PreferencesUtil.getString(keyLanguage, defaultValue: 'en');
  }
  
  // 字体大小
  static Future<void> setFontSize(double size) async {
    await PreferencesUtil.setString(keyFontSize, size.toString());
  }
  
  static double getFontSize() {
    final size = PreferencesUtil.getString(keyFontSize, defaultValue: '14.0');
    return double.tryParse(size) ?? 14.0;
  }
  
  // 通知设置
  static Future<void> setNotificationsEnabled(bool enabled) async {
    await PreferencesUtil.setBool(keyNotifications, enabled);
  }
  
  static bool getNotificationsEnabled() {
    return PreferencesUtil.getBool(keyNotifications, defaultValue: true);
  }
}

// 设置页面
class SettingsPage extends StatefulWidget {
  @override
  _SettingsPageState createState() => _SettingsPageState();
}

class _SettingsPageState extends State<SettingsPage> {
  String _themeMode = UserSettings.getThemeMode();
  String _language = UserSettings.getLanguage();
  double _fontSize = UserSettings.getFontSize();
  bool _notificationsEnabled = UserSettings.getNotificationsEnabled();
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Settings')),
      body: ListView(
        children: [
          ListTile(
            title: Text('Theme Mode'),
            subtitle: Text(_themeMode),
            onTap: _showThemePicker,
          ),
          ListTile(
            title: Text('Language'),
            subtitle: Text(_language),
            onTap: _showLanguagePicker,
          ),
          ListTile(
            title: Text('Font Size'),
            subtitle: Text('${_fontSize.toStringAsFixed(1)}'),
            onTap: _showFontSizePicker,
          ),
          SwitchListTile(
            title: Text('Notifications'),
            value: _notificationsEnabled,
            onChanged: (value) {
              setState(() {
                _notificationsEnabled = value;
                UserSettings.setNotificationsEnabled(value);
              });
            },
          ),
        ],
      ),
    );
  }
  
  void _showThemePicker() async {
    final result = await showDialog<String>(
      context: context,
      builder: (context) => SimpleDialog(
        title: Text('Select Theme Mode'),
        children: [
          SimpleDialogOption(
            child: Text('System'),
            onPressed: () => Navigator.pop(context, 'system'),
          ),
          SimpleDialogOption(
            child: Text('Light'),
            onPressed: () => Navigator.pop(context, 'light'),
          ),
          SimpleDialogOption(
            child: Text('Dark'),
            onPressed: () => Navigator.pop(context, 'dark'),
          ),
        ],
      ),
    );
    
    if (result != null) {
      setState(() {
        _themeMode = result;
        UserSettings.setThemeMode(result);
      });
    }
  }
  
  // 其他选择器实现...
}

最佳实践

  1. key 集中定义常量(文中 UserSettings 已是这个做法),别在业务代码里散落手写字符串:拼错一个字母就是读不到默认值,而且不报错,查起来极其痛苦。
  2. init() 在 main 里 await 一次,之后全同步读:getString 本身是同步的,别再给它包一层 async/await 脱裤子放屁。
  3. 存枚举、小配置转字符串,别塞大 JSON:单个 value 建议别超过几十 KB——Android 底层是整个 XML 文件读进内存再解析,value 越大每次 getInstance 越慢;大文本、长列表走文件或 SQLite。

注意事项

  1. Token、密码别存这里:Android 上是 XML 明文文件,root 机直接可读;敏感信息走 flutter_secure_storage(Keystore/Keychain),API 啰嗦一点但安全没有”差不多”。
  2. setXxx 返回 true 只代表写进了内存,落盘是异步的:App 被系统杀掉时,最后一次写入可能丢失——别拿它存”钱、订单状态”这种丢不起的数据。
  3. 别当高频计数器用:每秒记一次步数、滚动时实时存位置,每次 set 都会触发全量 XML 重写,费电还可能掉帧;高频数据放内存,退出时写一次。
  4. iOS 上数据进 NSUserDefaults,会跟随 iCloud 备份:别存设备相关的临时大块数据,用户换机恢复时会把垃圾也带过去,白白占用备份空间。

实战案例:Token 千万别明文塞进去

SharedPreferences 用起来太顺手,顺手到容易犯错。我在看 某物流冷链项目(一个货运 App)代码时发现,它的 lib/common/utils/storage.dart 把 SharedPreferences 封装得很漂亮,但登录 Token 是明文存进去的。这就是个现成的反面教材:SharedPreferences 在 Android 上是 XML 明文文件,root 过的设备或备份导出都能直接读到,Token 泄露等于账号泄露。我的取舍是:主题、语言、字体大小这种非敏感配置放心走 SharedPreferences(flutter_brightness 里也是这么用的);Token、refreshToken 一律走 flutter_secure_storage(Keystore/Keychain),读写是异步的、API 稍啰嗦,但安全这件事没有”差不多”。顺手提一句:封装工具类时给 key 加统一前缀(比如 app_),能避免和插件自己写的 key 撞车,这个坑我踩过。

总结

SharedPreferences 是一个简单但实用的数据持久化方案,适合存储应用配置和小型数据。通过合理封装和使用,可以方便地实现数据的本地存储功能。


分享这篇文章:


相关文章

上一篇
Flutter 路由参数传递详解
下一篇
Flutter 主题切换实现详解