MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

在 Flutter 中使用 SharedPreferences 存储复杂数据结构

2023-07-222.8k 阅读

Flutter 中的 SharedPreferences 基础

SharedPreferences 简介

在 Flutter 开发中,SharedPreferences 是一个轻量级的本地数据存储解决方案。它允许开发者在应用程序的本地设备上以键值对的形式存储简单的数据类型,例如布尔值、整数、浮点数、字符串以及字符串列表。这种存储方式非常适合用于保存应用程序的配置信息、用户设置以及一些临时性的少量数据。SharedPreferences 基于 Android 平台的 SharedPreferences 和 iOS 平台的 NSUserDefaults 实现,为 Flutter 开发者提供了统一的跨平台数据存储接口。

引入 SharedPreferences 依赖

要在 Flutter 项目中使用 SharedPreferences,首先需要在 pubspec.yaml 文件中添加依赖:

dependencies:
  shared_preferences: ^2.0.15

然后在终端运行 flutter pub get 命令来获取依赖。

简单数据类型的存储与读取

  1. 存储数据
    import 'package:shared_preferences/shared_preferences.dart';
    
    Future<void> saveSimpleData() async {
      final prefs = await SharedPreferences.getInstance();
      // 存储布尔值
      await prefs.setBool('isLoggedIn', true);
      // 存储整数
      await prefs.setInt('score', 100);
      // 存储浮点数
      await prefs.setDouble('version', 1.5);
      // 存储字符串
      await prefs.setString('username', 'JohnDoe');
      // 存储字符串列表
      await prefs.setStringList('hobbies', ['reading', 'writing']);
    }
    
  2. 读取数据
    import 'package:shared_preferences/shared_preferences.dart';
    
    Future<void> readSimpleData() async {
      final prefs = await SharedPreferences.getInstance();
      // 读取布尔值
      final bool? isLoggedIn = prefs.getBool('isLoggedIn');
      // 读取整数
      final int? score = prefs.getInt('score');
      // 读取浮点数
      final double? version = prefs.getDouble('version');
      // 读取字符串
      final String? username = prefs.getString('username');
      // 读取字符串列表
      final List<String>? hobbies = prefs.getStringList('hobbies');
      print('isLoggedIn: $isLoggedIn, score: $score, version: $version, username: $username, hobbies: $hobbies');
    }
    

复杂数据结构的挑战

复杂数据结构的定义

复杂数据结构在 Flutter 开发中可以指自定义类的实例、嵌套的列表和映射等。例如,假设我们有一个表示用户信息的类:

class User {
  String name;
  int age;
  List<String> interests;

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

这种包含多个不同类型属性的类实例,就是一种复杂数据结构。

传统 SharedPreferences 的局限

SharedPreferences 原生只支持简单数据类型的存储,如前面提到的布尔值、整数、浮点数、字符串和字符串列表。尝试直接存储一个 User 实例会导致错误,因为 SharedPreferences 不知道如何将 User 对象序列化为适合存储的格式。这就需要我们找到一种方法,将复杂数据结构转换为 SharedPreferences 能够理解的简单数据类型,存储后,在需要时再转换回原来的复杂数据结构。

将复杂数据结构转换为可存储格式

JSON 序列化与反序列化

  1. JSON 简介 JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,易于阅读和编写,同时也易于机器解析和生成。在 Flutter 中,dart:convert 库提供了对 JSON 序列化和反序列化的支持。

  2. 将复杂对象转换为 JSON 字符串 对于前面定义的 User 类,我们可以添加一个方法将其转换为 JSON 格式的字符串:

    import 'dart:convert';
    
    class User {
      String name;
      int age;
      List<String> interests;
    
      User({required this.name, required this.age, required this.interests});
    
      String toJsonString() {
        Map<String, dynamic> userMap = {
          'name': name,
          'age': age,
          'interests': interests
        };
        return json.encode(userMap);
      }
    }
    
  3. 从 JSON 字符串转换回复杂对象 同样,我们可以添加一个静态方法从 JSON 字符串创建 User 实例:

    import 'dart:convert';
    
    class User {
      String name;
      int age;
      List<String> interests;
    
      User({required this.name, required this.age, required this.interests});
    
      String toJsonString() {
        Map<String, dynamic> userMap = {
          'name': name,
          'age': age,
          'interests': interests
        };
        return json.encode(userMap);
      }
    
      static User fromJsonString(String jsonString) {
        Map<String, dynamic> userMap = json.decode(jsonString);
        return User(
          name: userMap['name'],
          age: userMap['age'],
          interests: List<String>.from(userMap['interests'])
        );
      }
    }
    

使用 Gson 类似库(Dart 中无 Gson,可类比)

虽然 Dart 没有像 Java 中 Gson 那样专门的库,但可以通过 json_serializable 等库实现类似功能。

  1. 引入 json_serializable 依赖pubspec.yaml 文件中添加:
dependencies:
  json_annotation: ^4.8.1
dev_dependencies:
  build_runner: ^2.3.3
  json_serializable: ^6.6.1

然后运行 flutter pub get

  1. 定义可序列化的类 修改 User 类:
import 'package:json_annotation/json_annotation.dart';

part 'user.g.dart';

@JsonSerializable()
class User {
  String name;
  int age;
  List<String> interests;

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

  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
  Map<String, dynamic> toJson() => _$UserToJson(this);
}

这里 part 'user.g.dart'; 表示会生成一个名为 user.g.dart 的文件,其中包含自动生成的序列化和反序列化代码。

  1. 生成序列化和反序列化代码 在终端运行 flutter pub run build_runner build,这会在项目目录下生成 user.g.dart 文件,其中包含了 fromJsontoJson 的具体实现,简化了手动编写 JSON 转换代码的过程。

在 SharedPreferences 中存储复杂数据结构

存储复杂对象

  1. 使用 JSON 字符串存储 假设我们有一个 User 实例,我们可以将其转换为 JSON 字符串并存储到 SharedPreferences 中:
import 'package:shared_preferences/shared_preferences.dart';

Future<void> saveUser() async {
  final user = User(name: 'Alice', age: 25, interests: ['music', 'travel']);
  final prefs = await SharedPreferences.getInstance();
  final jsonString = user.toJsonString();
  await prefs.setString('userData', jsonString);
}
  1. 使用 json_serializable 存储 如果使用 json_serializable,存储过程类似:
import 'package:shared_preferences/shared_preferences.dart';

Future<void> saveUserWithJsonSerializable() async {
  final user = User(name: 'Bob', age: 30, interests: ['sports', 'cooking']);
  final prefs = await SharedPreferences.getInstance();
  final jsonMap = user.toJson();
  final jsonString = json.encode(jsonMap);
  await prefs.setString('userDataSerializable', jsonString);
}

读取复杂对象

  1. 从 JSON 字符串读取 读取存储的 User 实例:
import 'package:shared_preferences/shared_preferences.dart';

Future<User?> readUser() async {
  final prefs = await SharedPreferences.getInstance();
  final jsonString = prefs.getString('userData');
  if (jsonString != null) {
    return User.fromJsonString(jsonString);
  }
  return null;
}
  1. 从 json_serializable 生成的格式读取 从使用 json_serializable 存储的数据中读取 User 实例:
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:convert';

Future<User?> readUserWithJsonSerializable() async {
  final prefs = await SharedPreferences.getInstance();
  final jsonString = prefs.getString('userDataSerializable');
  if (jsonString != null) {
    final jsonMap = json.decode(jsonString);
    return User.fromJson(jsonMap);
  }
  return null;
}

处理嵌套的复杂数据结构

嵌套列表和映射

假设我们有一个更复杂的数据结构,包含嵌套的列表和映射:

class Company {
  String name;
  List<User> employees;
  Map<String, List<String>> departments;

  Company({required this.name, required this.employees, required this.departments});
}
  1. 转换为 JSON 字符串
import 'dart:convert';

class Company {
  String name;
  List<User> employees;
  Map<String, List<String>> departments;

  Company({required this.name, required this.employees, required this.departments});

  String toJsonString() {
    Map<String, dynamic> companyMap = {
      'name': name,
      'employees': employees.map((user) => json.decode(user.toJsonString())).toList(),
      'departments': departments
    };
    return json.encode(companyMap);
  }
}
  1. 从 JSON 字符串转换回对象
import 'dart:convert';

class Company {
  String name;
  List<User> employees;
  Map<String, List<String>> departments;

  Company({required this.name, required this.employees, required this.departments});

  String toJsonString() {
    Map<String, dynamic> companyMap = {
      'name': name,
      'employees': employees.map((user) => json.decode(user.toJsonString())).toList(),
      'departments': departments
    };
    return json.encode(companyMap);
  }

  static Company fromJsonString(String jsonString) {
    Map<String, dynamic> companyMap = json.decode(jsonString);
    List<User> employees = companyMap['employees'].map<User>((employeeJson) {
      return User.fromJsonString(json.encode(employeeJson));
    }).toList();
    return Company(
      name: companyMap['name'],
      employees: employees,
      departments: Map<String, List<String>>.from(companyMap['departments'])
    );
  }
}

存储和读取嵌套结构

  1. 存储嵌套结构
import 'package:shared_preferences/shared_preferences.dart';

Future<void> saveCompany() async {
  final user1 = User(name: 'Eve', age: 28, interests: ['painting', 'photography']);
  final user2 = User(name: 'Frank', age: 32, interests: ['programming', 'gaming']);
  final company = Company(
    name: 'TechCorp',
    employees: [user1, user2],
    departments: {
      'Engineering': ['user1', 'user2'],
      'Marketing': []
    }
  );
  final prefs = await SharedPreferences.getInstance();
  final jsonString = company.toJsonString();
  await prefs.setString('companyData', jsonString);
}
  1. 读取嵌套结构
import 'package:shared_preferences/shared_preferences.dart';

Future<Company?> readCompany() async {
  final prefs = await SharedPreferences.getInstance();
  final jsonString = prefs.getString('companyData');
  if (jsonString != null) {
    return Company.fromJsonString(jsonString);
  }
  return null;
}

注意事项与优化

数据一致性

  1. 更新与删除 当复杂数据结构发生变化时,需要及时更新 SharedPreferences 中的数据。例如,如果 User 的年龄发生变化:
Future<void> updateUserAge() async {
  final prefs = await SharedPreferences.getInstance();
  final jsonString = prefs.getString('userData');
  if (jsonString != null) {
    User user = User.fromJsonString(jsonString);
    user.age = 26;
    final newJsonString = user.toJsonString();
    await prefs.setString('userData', newJsonString);
  }
}

如果需要删除复杂数据,直接使用 SharedPreferencesremove 方法:

Future<void> deleteUser() async {
  final prefs = await SharedPreferences.getInstance();
  await prefs.remove('userData');
}
  1. 数据版本控制 随着应用的更新,复杂数据结构可能会发生变化。可以在存储数据时添加版本号,例如:
Future<void> saveUserWithVersion() async {
  final user = User(name: 'Charlie', age: 22, interests: ['dancing', 'hiking']);
  final prefs = await SharedPreferences.getInstance();
  Map<String, dynamic> userMap = {
  'version': 1,
    'userData': json.decode(user.toJsonString())
  };
  final jsonString = json.encode(userMap);
  await prefs.setString('userDataWithVersion', jsonString);
}

读取时检查版本号并进行相应处理:

Future<User?> readUserWithVersion() async {
  final prefs = await SharedPreferences.getInstance();
  final jsonString = prefs.getString('userDataWithVersion');
  if (jsonString != null) {
    Map<String, dynamic> userMap = json.decode(jsonString);
    int version = userMap['version'];
    if (version == 1) {
      return User.fromJsonString(json.encode(userMap['userData']));
    } else {
      // 处理版本不兼容情况
      return null;
    }
  }
  return null;
}

性能优化

  1. 批量操作 尽量减少对 SharedPreferences 的频繁读写操作。如果需要存储多个复杂数据结构,可以将它们组合成一个大的 JSON 对象,然后一次性存储:
Future<void> saveMultipleComplexData() async {
  final user1 = User(name: 'David', age: 24, interests: ['reading', 'writing']);
  final user2 = User(name: 'Ella', age: 27, interests: ['swimming', 'running']);
  final company = Company(
    name: 'HealthCo',
    employees: [user1, user2],
    departments: {
      'Fitness': ['user1', 'user2'],
      'Nutrition': []
    }
  );
  Map<String, dynamic> combinedData = {
    'user1': json.decode(user1.toJsonString()),
    'user2': json.decode(user2.toJsonString()),
    'company': json.decode(company.toJsonString())
  };
  final prefs = await SharedPreferences.getInstance();
  final jsonString = json.encode(combinedData);
  await prefs.setString('combinedComplexData', jsonString);
}

读取时同样一次性读取并解析:

Future<Map<String, dynamic>?> readMultipleComplexData() async {
  final prefs = await SharedPreferences.getInstance();
  final jsonString = prefs.getString('combinedComplexData');
  if (jsonString != null) {
    return json.decode(jsonString);
  }
  return null;
}
  1. 缓存机制 在应用内可以实现简单的缓存机制,避免每次都从 SharedPreferences 读取数据。例如,使用一个全局变量缓存 User 实例:
User? cachedUser;

Future<User?> getUser() async {
  if (cachedUser != null) {
    return cachedUser;
  }
  final user = await readUser();
  if (user != null) {
    cachedUser = user;
  }
  return user;
}

通过以上详细的介绍、代码示例以及注意事项和优化建议,开发者可以在 Flutter 中有效地使用 SharedPreferences 存储复杂数据结构,提升应用的数据管理能力和用户体验。