深入解析:在 Flutter 中使用 SharedPreferences 高效读写数据

在开发 Flutter 应用时,我们经常会遇到需要持久化保存少量数据的需求。比如,你想保存用户的登录状态、应用的主题设置(深色模式或浅色模式),或者仅仅是一个简单的计数器。这时候,引入一个完整的数据库(如 SQLite)往往显得有些“杀鸡用牛刀”,不仅增加了应用的复杂度,还可能影响性能。那么,有没有一种轻量级、简单且高效的解决方案呢?答案是肯定的。

在本文中,我们将深入探讨 Flutter 中的 SharedPreferences。我们将一起学习如何利用这个强大的工具来以键值对的形式存储和读取原始数据(如 String、int、bool 等)。无论你是刚入门的开发者,还是希望巩固基础知识的资深工程师,这篇指南都将带你从零开始,通过实战代码示例,掌握在 Flutter 中处理本地轻量级数据存储的最佳实践。

为什么选择 SharedPreferences?

让我们先来思考一个实际场景:假设你的应用有一个“登录”页面。当用户第一次成功登录后,你肯定不希望他们每次打开应用都要重新输入用户名和密码。为了提升用户体验,我们需要记住用户的登录状态。

虽然我们可以将这个信息存放在数据库中,但仅仅为了存储一个 INLINECODE22377864(布尔值)或者 INLINECODEc19ac3ae(字符串)而建立一张数据库表,显然是不划算的。这时,SharedPreferences 就成了最佳选择。它通过简单的键值对机制,将数据以 XML 文件的形式(在 Android 上)或类似的持久化方式(在 iOS 和 Web 上)保存在设备中。读写速度极快,且 API 非常直观。

准备工作:安装与配置

在我们开始编写代码之前,首先需要将 shared_preferences 插件添加到我们的项目中。请按照以下步骤操作,确保你的开发环境已经准备就绪。

#### 1. 添加依赖

打开你项目根目录下的 INLINECODE7dcce9fd 文件。这是一个管理项目依赖的核心配置文件。请在 INLINECODEe22ee247 部分添加 shared_preferences 包。

dependencies:
  flutter:
    sdk: flutter
  shared_preferences: ^2.2.2 # 请使用最新版本

添加完这段代码后,保存文件。接下来,你需要获取这个依赖包。

#### 2. 安装依赖

在终端中运行以下命令来下载并安装包:

flutter pub get

提示:如果你使用的是 VS Code 或 Android Studio,通常在保存 pubspec.yaml 后,IDE 会自动提示你执行“Flutter pub get”,你只需要点击按钮即可。

核心概念:实例化

与很多 Flutter 插件不同,SharedPreferences 不需要复杂的初始化配置。我们主要通过获取一个单例实例来开始操作。

在读写任何数据之前,我们必须调用异步方法来获取这个实例:

final prefs = await SharedPreferences.getInstance();

因为涉及到 I/O 操作(读取设备存储),所以这是一个异步方法。通常我们会在 INLINECODEf68cbc50 或其他异步函数中调用它。一旦获取了 INLINECODEa9d91e1d 实例,我们就可以进行各种操作了。

实战演练:数据的增删改查

让我们通过具体的代码示例来看看如何进行数据的写入、读取和删除。我们将涵盖最常用的数据类型。

#### 1. 写入数据

写入数据非常简单,SharedPreferences 提供了以 INLINECODEf4f10654 开头的一系列方法,如 INLINECODEf968c234, INLINECODE9b752f36, INLINECODEfeb22570 等。注意,所有的写入方法都是异步的,并返回一个 Future,表示保存是否成功。

Future _saveData() async {
  // 获取实例
  final prefs = await SharedPreferences.getInstance();

  // 保存整数
  await prefs.setInt(‘counter‘, 10);

  // 保存布尔值(例如:记住登录状态)
  await prefs.setBool(‘isUserLoggedIn‘, true);

  // 保存浮点数
  await prefs.setDouble(‘price‘, 99.99);

  // 保存字符串
  await prefs.setString(‘username‘, ‘FlutterDev‘);

  // 保存字符串列表(例如:搜索历史)
  await prefs.setStringList(‘cartItems‘, [‘Apple‘, ‘Banana‘, ‘Mango‘]);

  print("数据保存成功!");
}

关键点解析:

  • 键的唯一性:数据是通过唯一的键来标识的。如果你对同一个键(如 ‘counter‘)再次调用 setInt,旧的值会被覆盖。
  • 异步操作:不要忘记使用 await,确保数据真正写入后再执行后续逻辑,虽然在大多数情况下这很快,但在低性能设备上可能会造成阻塞。

#### 2. 读取数据

读取数据时,如果键不存在,方法会返回 INLINECODE2a7fd4c1(除了某些特定类型有默认值,但通常都需要处理空值情况)。为了增加代码的健壮性,我们可以利用 Dart 的 null-aware 操作符 INLINECODEba5f0082 来提供默认值。

void _readData() async {
  final prefs = await SharedPreferences.getInstance();

  // 读取整数,如果不存在则默认为 0
  final int? counter = prefs.getInt(‘counter‘) ?? 0;

  // 读取布尔值,默认为 false
  final bool? isLoggedIn = prefs.getBool(‘isUserLoggedIn‘) ?? false;

  // 读取字符串
  final String? username = prefs.getString(‘username‘);

  // 读取列表
  final List? cartItems = prefs.getStringList(‘cartItems‘);

  print("读取到的数据: Counter=$counter, LoggedIn=$isLoggedIn, User=$username");
}

实战建议:

在读取数据时,始终考虑“如果这是用户第一次打开应用怎么办?”因为第一次读取时,键是不存在的。返回 INLINECODE259a85fc 可能会导致 INLINECODEf12e3ec1,所以提供一个默认值(如 ?? 0)是一个非常好的习惯。

#### 3. 删除数据

当你不再需要某个数据,或者用户选择了“清除缓存”时,你可以使用 remove 方法。

Future _clearData() async {
  final prefs = await SharedPreferences.getInstance();

  // 删除特定的键(例如 ‘username‘)
  await prefs.remove(‘username‘);

  print("用户名已删除");
}

如果你希望一次性清除所有存储的数据(相当于重置应用),可以使用 clear() 方法:

Future _clearAllPreferences() async {
  final prefs = await SharedPreferences.getInstance();
  await prefs.clear();
  print("所有偏好设置已重置");
}

完整项目示例:构建一个计数器与登录状态模拟器

光说不练假把式。让我们通过构建一个稍微复杂一点的完整应用来巩固我们的知识。

项目功能:

  • 计数器功能:点击按钮增加数字,并保存。关闭应用后重新打开,数字依然存在。
  • 登录状态模拟:切换“登录/登出”开关,应用会记住你的选择。
  • UI 展示:界面友好,实时显示当前状态。

#### 第一步:项目结构

我们将创建一个新的 Flutter 项目:

flutter create shared_pref_demo
cd shared_pref_demo

#### 第二步:编写业务逻辑类

为了保持代码的整洁,遵循 MVC 或 MVVM 模式,我们不直接在 UI 代码中写存储逻辑,而是创建一个辅助类。

创建一个名为 lib/pref_service.dart 的文件:

import ‘package:shared_preferences/shared_preferences.dart‘;

class PrefService {
  // 定义键名常量,避免硬编码字符串导致拼写错误
  static const String _keyCounter = ‘counter_value‘;
  static const String _keyLoginStatus = ‘is_logged_in‘;

  // 保存计数器数值
  Future saveCounter(int value) async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.setInt(_keyCounter, value);
  }

  // 读取计数器数值
  Future getCounter() async {
    final prefs = await SharedPreferences.getInstance();
    // 返回值,如果未找到则返回 0
    return prefs.getInt(_keyCounter) ?? 0;
  }

  // 保存登录状态
  Future saveLoginStatus(bool isLoggedIn) async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.setBool(_keyLoginStatus, isLoggedIn);
  }

  // 读取登录状态
  Future getLoginStatus() async {
    final prefs = await SharedPreferences.getInstance();
    return prefs.getBool(_keyLoginStatus) ?? false;
  }

  // 清除所有数据
  Future clearAll() async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.clear();
  }
}

#### 第三步:构建用户界面

修改 INLINECODEd517d43f 文件。我们将使用 INLINECODE79cf5918 来管理界面的变化,并在初始化时读取数据。

import ‘package:flutter/material.dart‘;
import ‘pref_service.dart‘; // 引入我们刚才创建的服务类

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: ‘SharedPreferences 完整示例‘,
      theme: ThemeData(
        primarySwatch: Colors.blue,
        useMaterial3: true,
      ),
      home: const HomePage(),
    );
  }
}

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

  @override
  State createState() => _HomePageState();
}

class _HomePageState extends State {
  // 实例化服务类
  final PrefService _prefService = PrefService();

  // 状态变量
  int _counter = 0;
  bool _isLoggedIn = false;

  @override
  void initState() {
    super.initState();
    _loadData(); // 启动时加载数据
  }

  // 从本地存储加载数据的方法
  Future _loadData() async {
    final counter = await _prefService.getCounter();
    final isLoggedIn = await _prefService.getLoginStatus();
    
    // 更新 UI 状态
    setState(() {
      _counter = counter;
      _isLoggedIn = isLoggedIn;
    });
  }

  // 增加计数器并保存
  Future _incrementCounter() async {
    setState(() {
      _counter++;
    });
    await _prefService.saveCounter(_counter);
  }

  // 切换登录状态并保存
  Future _toggleLogin() async {
    setState(() {
      _isLoggedIn = !_isLoggedIn;
    });
    await _prefService.saveLoginStatus(_isLoggedIn);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text(‘SharedPreferences Demo‘),
        actions: [
          IconButton(
            icon: const Icon(Icons.delete_outline),
            onPressed: () async {
              await _prefService.clearAll();
              _loadData(); // 重置后重新加载
              ScaffoldMessenger.of(context).showSnackBar(
                const SnackBar(content: Text(‘所有数据已清除‘)),
              );
            },
          ),
        ],
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            // 登录状态展示部分
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Icon(
                  _isLoggedIn ? Icons.lock_open : Icons.lock,
                  color: _isLoggedIn ? Colors.green : Colors.red,
                ),
                const SizedBox(width: 10),
                Text(
                  _isLoggedIn ? "状态: 已登录" : "状态: 未登录",
                  style: const TextStyle(fontSize: 18),
                ),
              ],
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: _toggleLogin,
              child: Text(_isLoggedIn ? "点击登出" : "点击登录"),
            ),
            const Divider(height: 50, thickness: 2),
            
            // 计数器展示部分
            const Text(
              ‘你按下的次数:‘,
              style: TextStyle(fontSize: 20),
            ),
            Text(
              ‘$_counter‘,
              style: const TextStyle(fontSize: 42, fontWeight: FontWeight.bold),
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: ‘增加‘,
        child: const Icon(Icons.add),
      ),
    );
  }
}

代码解析:

  • 数据隔离:我们将所有对 SharedPreferences 的直接调用都封装在了 PrefService 类中。这意味着如果你将来想要切换存储方式(比如改用 Hive 或 SQLite),你只需要修改这一个文件,而不需要改动 UI 代码。这是工程化开发的一个重要原则。
  • 异步初始化:注意看 INLINECODE882b693d 方法,它调用了 INLINECODEb29e22a4。当应用启动时,INLINECODE374a6a9f 会异步去读取文件,一旦拿到数据,就通过 INLINECODEf5241fd4 刷新界面。由于文件读取速度极快,用户通常感觉不到延迟。
  • 状态持久化:每次 INLINECODE7f65a3e7 增加或登录状态改变,我们都会立即调用 INLINECODE70df5cde 方法。这意味着即使你此时直接杀掉应用进程(不仅仅是退出到桌面),下次打开时数据依然存在。

最佳实践与常见陷阱

在使用 SharedPreferences 时,有一些经验法则和常见错误需要我们注意,这能帮你省去很多调试时间。

#### 1. 不要存储敏感信息

警告: SharedPreferences 虽然方便,但它不是安全的存储方案。在 Android 中,它的数据存储在 XML 文件中,任何拥有 Root 权限的用户或者恶意软件都可以轻松读取这些文件。因此,绝对不要在这里存储密码、Token、银行账号等敏感信息。对于敏感数据,请使用 fluttersecurestorage 或其他加密存储方案。

#### 2. 键的管理

正如上面的示例中那样,建议将所有的键定义为 static const 字符串。这不仅能防止你在代码中手抖拼错单词(比如 ‘usrename‘),还能利用 IDE 的自动补全功能提高开发效率。

#### 3. 数据量限制

SharedPreferences 是为少量数据设计的。如果你试图存入一个巨大的 JSON 字符串或者巨大的图片 Base64 编码,可能会导致 ANR(应用无响应)或者加载缓慢。如果你有大量结构化数据,请考虑 SQLite 或 Hive。

#### 4. 键是否存在?

SharedPreferences 没有原生的 INLINECODE82fa8a08 方法直接在实例上调用(虽然可以通过 INLINECODE3f320a4a 检查),但通常我们通过 INLINECODEa85607bd 方法返回 INLINECODEdffc8f56 来判断。如果你需要一个严谨的检查逻辑,可以这样写:

Future checkKeyExists(String key) async {
  final prefs = await SharedPreferences.getInstance();
  return prefs.containsKey(key);
}

总结与展望

通过这篇文章,我们从基础概念出发,逐步深入,构建了一个功能完整的 Flutter 应用,学会了如何利用 SharedPreferences 来持久化存储整型、布尔型等数据。我们掌握了数据的写入、读取、删除以及清除的全部流程,并了解了封装服务类和注意数据安全性的最佳实践。

现在,你已经拥有了一个强大的工具来处理应用中的轻量级数据存储需求。

下一步建议:

你可以尝试扩展当前的项目,比如添加一个功能,将用户的“个人简介”也保存下来,或者尝试结合 Provider 状态管理库,当用户登录状态改变时,自动通知并更新整个应用的界面。

希望这篇指南能对你的 Flutter 开发之旅有所帮助!如果你在实践过程中遇到任何问题,欢迎在评论区留言交流。

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。如需转载,请注明文章出处豆丁博客和来源网址。https://shluqu.cn/25132.html
点赞
0.00 平均评分 (0% 分数) - 0