Flutter—— 本地存储(shared_preferences)

一、简介

shared_preferences 是 Flutter 官方提供的键值对(Key-Value) 本地存储插件,本质是对原生平台存储的封装:

  • iOS:封装 NSUserDefaults
  • Android:封装 SharedPreferences
  • 桌面端(Windows/macOS):封装本地 JSON 文件
  • 核心特点:轻量、API 简单、持久化(APP 重启 / 卸载前数据不丢失)、仅支持基础数据类型

二、支持的数据类型

类型 对应 API 方法 说明
字符串 setString()/getString() 存储 token、用户名等
布尔值 setBool()/getBool() 存储开关状态、是否登录等
整数 setInt()/getInt() 存储计数、ID 等
浮点数 setDouble()/getDouble() 存储版本号、数值配置等
字符串列表 setStringList()/getStringList() 存储历史记录、标签等

三、基础使用步骤

1. 安装依赖

pub.dev/packages/sh...

pubspec.yaml 中添加:

yaml 复制代码
dependencies:
  flutter:
    sdk: flutter
  shared_preferences: ^2.5.4 # 推荐使用最新稳定版

2. API 使用

dart 复制代码
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'SharedPreferences 详解',
      home: const SPExamplePage(),
    );
  }
}

class SPExamplePage extends StatefulWidget {
  const SPExamplePage({super.key});

  @override
  State<SPExamplePage> createState() => _SPExamplePageState();
}

class _SPExamplePageState extends State<SPExamplePage> {
  // 存储的测试数据
  String _userToken = "";
  bool _isDarkMode = false;
  int _loginCount = 0;
  double _appVersion = 1.0;
  List<String> _historyList = [];

  // 初始化:页面加载时读取存储的数据
  @override
  void initState() {
    super.initState();
    _loadAllData();
  }

  // ========== 核心方法1:读取数据 ==========
  Future<void> _loadAllData() async {
    // 1. 获取 SharedPreferences 实例(必须异步)
    SharedPreferences prefs = await SharedPreferences.getInstance();

    // 2. 读取数据:第二个参数是「默认值」(key不存在时返回)
    setState(() {
      _userToken = prefs.getString("user_token") ?? ""; // 字符串默认空
      _isDarkMode = prefs.getBool("dark_mode") ?? false; // 布尔默认false
      _loginCount = prefs.getInt("login_count") ?? 0; // 整数默认0
      _appVersion = prefs.getDouble("app_version") ?? 1.0; // 浮点数默认1.0
      _historyList = prefs.getStringList("browse_history") ?? []; // 列表默认空
    });
  }

  // ========== 核心方法2:保存数据 ==========
  Future<void> _saveAllData() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();

    // 1. 保存单个数据
    await prefs.setString("user_token", "abc123456789");
    await prefs.setBool("dark_mode", true);
    await prefs.setInt("login_count", _loginCount + 1); // 计数+1
    await prefs.setDouble("app_version", 2.1);
    await prefs.setStringList("browse_history", ["首页", "我的", "设置"]);

    // 2. 保存后刷新页面数据
    _loadAllData();

    // 提示用户
    if (mounted) { // 防止页面销毁后调用context
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text("数据保存成功 ✅")),
      );
    }
  }

  // ========== 核心方法3:删除数据 ==========
  Future<void> _deleteData() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();

    // 方式1:删除单个key
    await prefs.remove("user_token");

    // 方式2:清空所有数据(谨慎使用!)
    // await prefs.clear();

    // 刷新数据
    _loadAllData();
    if (mounted) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text("数据删除成功 ❌")),
      );
    }
  }

  // ========== 核心方法4:检查key是否存在 ==========
  Future<void> _checkKeyExists() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    bool hasToken = prefs.containsKey("user_token");
    if (mounted) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text("user_token 是否存在:$hasToken")),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("SharedPreferences 详解")),
      body: Padding(
        padding: const EdgeInsets.all(20),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // 显示存储的数据
            Text("用户Token:$_userToken"),
            Text("深色模式:$_isDarkMode"),
            Text("登录次数:$_loginCount"),
            Text("APP版本:$_appVersion"),
            Text("浏览历史:${_historyList.join(", ")}"),
            const SizedBox(height: 30),

            // 操作按钮
            Row(
              children: [
                ElevatedButton(
                  onPressed: _saveAllData,
                  child: const Text("保存数据"),
                ),
                const SizedBox(width: 10),
                ElevatedButton(
                  onPressed: _deleteData,
                  style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
                  child: const Text("删除Token"),
                ),
              ],
            ),
            const SizedBox(height: 10),
            ElevatedButton(
              onPressed: _checkKeyExists,
              child: const Text("检查Token是否存在"),
            ),
          ],
        ),
      ),
    );
  }
}

四、进阶技巧

1. 封装工具类(避免重复代码)

实际项目中建议封装成单例工具类,统一管理存储的 key 和方法:

csharp 复制代码
import 'package:shared_preferences/shared_preferences.dart';

class SPUtil {
  // 单例模式
  static SPUtil? _instance;
  static SharedPreferences? _prefs;

  // 私有化构造函数
  SPUtil._();

  // 获取单例
  static Future<SPUtil> getInstance() async {
    if (_instance == null) {
      _instance = SPUtil._();
    }
    if (_prefs == null) {
      _prefs = await SharedPreferences.getInstance();
    }
    return _instance!;
  }

  // ========== 定义存储的key(统一管理,避免拼写错误) ==========
  static const String KEY_USER_TOKEN = "user_token";
  static const String KEY_DARK_MODE = "dark_mode";
  static const String KEY_LOGIN_COUNT = "login_count";

  // ========== 封装常用方法 ==========
  // 保存字符串
  Future<void> setString(String key, String value) async {
    await _prefs?.setString(key, value);
  }

  // 读取字符串
  String getString(String key, {String defaultValue = ""}) {
    return _prefs?.getString(key) ?? defaultValue;
  }

  // 保存布尔值
  Future<void> setBool(String key, bool value) async {
    await _prefs?.setBool(key, value);
  }

  // 读取布尔值
  bool getBool(String key, {bool defaultValue = false}) {
    return _prefs?.getBool(key) ?? defaultValue;
  }

  // 删除单个key
  Future<void> remove(String key) async {
    await _prefs?.remove(key);
  }

  // 清空所有数据
  Future<void> clear() async {
    await _prefs?.clear();
  }
}

// 使用示例
void useSPUtil() async {
  SPUtil spUtil = await SPUtil.getInstance();
  // 保存
  await spUtil.setString(SPUtil.KEY_USER_TOKEN, "123456");
  // 读取
  String token = spUtil.getString(SPUtil.KEY_USER_TOKEN);
  print("Token:$token");
}

2. 存储复杂对象(序列化 / 反序列化)

shared_preferences 不支持直接存储对象,需先转 JSON 字符串:

dart 复制代码
import 'dart:convert';

// 定义用户模型
class User {
  String name;
  int age;
  String email;

  User({required this.name, required this.age, required this.email});

  // 转JSON字符串
  String toJson() {
    Map<String, dynamic> map = {
      "name": name,
      "age": age,
      "email": email,
    };
    return json.encode(map);
  }

  // 从JSON字符串转对象
  static User fromJson(String jsonStr) {
    Map<String, dynamic> map = json.decode(jsonStr);
    return User(
      name: map["name"],
      age: map["age"],
      email: map["email"],
    );
  }
}

// 存储/读取对象
Future<void> saveUser() async {
  SharedPreferences prefs = await SharedPreferences.getInstance();
  // 1. 创建对象
  User user = User(name: "张三", age: 25, email: "zhangsan@example.com");
  // 2. 转JSON字符串保存
  await prefs.setString("user_info", user.toJson());
  // 3. 读取并转对象
  String userJson = prefs.getString("user_info") ?? "";
  if (userJson.isNotEmpty) {
    User savedUser = User.fromJson(userJson);
    print("用户名:${savedUser.name},年龄:${savedUser.age}");
  }
}

五、避坑指南(常见问题)

1. 同步 / 异步问题(最容易踩坑)

  • ❌ 错误:在 initState 中同步调用 getStringgetInstance 是异步的)

    typescript 复制代码
    @override
    void initState() {
      super.initState();
      // 错误!SharedPreferences.getInstance() 是异步,不能直接同步调用
      String token = SharedPreferences.getInstance().then((prefs) => prefs.getString("token"));
    }
  • ✅ 正确:用 async/awaitthen 处理异步

    typescript 复制代码
    @override
    void initState() {
      super.initState();
      _loadData(); // 异步方法
    }
    
    Future<void> _loadData() async {
      SharedPreferences prefs = await SharedPreferences.getInstance();
      String token = prefs.getString("token") ?? "";
    }

2. 页面销毁后调用 setState

  • 问题:异步操作完成后页面已销毁,调用 setState 会报错

  • 解决:用 mounted 判断页面是否挂载

    ini 复制代码
    Future<void> _loadData() async {
      SharedPreferences prefs = await SharedPreferences.getInstance();
      if (mounted) { // 关键:判断页面是否还在
        setState(() {
          _token = prefs.getString("token") ?? "";
        });
      }
    }

3. 数据未及时刷新

  • 问题:保存数据后,UI 没有实时更新

  • 解决:保存后重新调用读取方法,触发 setState

    csharp 复制代码
    await prefs.setString("token", "new_token");
    _loadData(); // 重新读取,刷新UI
相关推荐
程序员Ctrl喵18 小时前
异步编程:Event Loop 与 Isolate 的深层博弈
开发语言·flutter
前端不太难19 小时前
Flutter 如何设计可长期维护的模块边界?
flutter
小蜜蜂嗡嗡20 小时前
flutter列表中实现置顶动画
flutter
始持21 小时前
第十二讲 风格与主题统一
前端·flutter
始持21 小时前
第十一讲 界面导航与路由管理
flutter·vibecoding
始持21 小时前
第十三讲 异步操作与异步构建
前端·flutter
新镜21 小时前
【Flutter】 视频视频源横向、竖向问题
flutter
黄林晴1 天前
Compose Multiplatform 1.10 发布:统一 Preview、Navigation 3、Hot Reload 三箭齐发
android·flutter
Swift社区1 天前
Flutter 应该按功能拆,还是按技术层拆?
flutter
肠胃炎1 天前
树形选择器组件封装
前端·flutter