Flutter for OpenHarmony 实战:数据持久化方案深度解析

摘要在这里插入图片描述

数据持久化是移动应用开发的核心技术之一。本文以记账助手应用为例,深入讲解Flutter for OpenHarmony平台上的数据持久化方案,包括SharedPreferences的使用方法、JSON序列化技术、数据模型设计最佳实践等。通过本文学习,读者将掌握Flutter应用中数据存储的完整实现方案,了解不同存储场景的最佳选择。


一、移动应用数据存储概述

1.1 为什么需要数据持久化

移动应用中的数据持久化是指将数据保存到非易失性存储介质中,确保应用关闭后数据不会丢失。

应用场景

  • 用户偏好设置
  • 业务数据存储
  • 缓存临时数据
  • 离线数据支持

持久化的价值

  • 提升用户体验
  • 支持离线使用
  • 保障数据安全
  • 实现跨会话状态保持

1.2 Flutter平台常用存储方案

存储方案 适用场景 数据类型 复杂度
SharedPreferences 简单键值对存储 基本类型
SQLite 结构化数据存储 复杂关系数据
文件存储 大文件存储 文件、图片
Hive NoSQL数据库 复杂对象

1.3 选择合适的存储方案

选择SharedPreferences的场景

  • 数据量小(通常少于1MB)
  • 数据结构简单
  • 需要快速读写
  • 主要是键值对数据

选择SQLite的场景

  • 数据量大
  • 需要复杂查询
  • 数据关系复杂
  • 需要事务支持

二、SharedPreferences详解

2.1 SharedPreferences简介

SharedPreferences是Flutter平台上常用的轻量级存储方案,通过键值对的方式存储数据。

核心特性

  • 异步读写操作
  • 支持基本数据类型
  • 线程安全
  • 自动持久化

支持的数据类型

  • String(字符串)
  • int(整数)
  • double(浮点数)
  • bool(布尔值)
  • StringList(字符串列表)

2.2 添加依赖

在pubspec.yaml中添加依赖:

dependencies:
  flutter:
    sdk: flutter
  shared_preferences: ^2.2.2

安装依赖:

flutter pub get

2.3 基本使用方法

获取实例

final prefs = await SharedPreferences.getInstance();

写入数据

// 保存字符串
await prefs.setString('username', '张三');

// 保存整数
await prefs.setInt('age', 25);

// 保存布尔值
await prefs.setBool('isLoggedIn', true);

// 保存浮点数
await prefs.setDouble('balance', 1234.56);

// 保存字符串列表
await prefs.setStringList('tags', ['工作', '生活', '学习']);

读取数据

// 读取字符串(带默认值)
String username = prefs.getString('username') ?? '匿名用户';

// 读取整数(带默认值)
int age = prefs.getInt('age') ?? 0;

// 读取布尔值(带默认值)
bool isLoggedIn = prefs.getBool('isLoggedIn') ?? false;

// 读取浮点数(带默认值)
double balance = prefs.getDouble('balance') ?? 0.0;

// 读取字符串列表(带默认值)
List<String> tags = prefs.getStringList('tags') ?? [];

删除数据

// 删除单个键
await prefs.remove('username');

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

检查键是否存在

bool hasKey = prefs.containsKey('username');

2.4 异步操作最佳实践

SharedPreferences的所有写操作都是异步的,需要正确处理:

// 好的做法:使用await等待完成
Future<void> saveUserData() async {
  final prefs = await SharedPreferences.getInstance();
  await prefs.setString('username', '张三');
  await prefs.setInt('age', 25);
  // 保存完成后的操作
  print('数据保存成功');
}

// 不好的做法:不等待完成
void saveUserDataBad() {
  SharedPreferences.getInstance().then((prefs) {
    prefs.setString('username', '张三');
    // 无法确定何时保存完成
  });
}

三、JSON序列化技术

3.1 JSON简介

JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,易于人阅读和编写,同时也易于机器解析和生成。

基本数据类型

  • 对象(花括号包围)
  • 数组(方括号包围)
  • 字符串(双引号包围)
  • 数字(整数或浮点数)
  • 布尔值(true/false)
  • null

3.2 Dart中的JSON处理

Dart提供了dart:convert库来处理JSON数据:

import 'dart:convert';

JSON编码(序列化)

// 将对象转换为JSON字符串
Map<String, dynamic> data = {
  'name': '张三',
  'age': 25,
  'isActive': true,
};

String jsonString = jsonEncode(data);
print(jsonString); // {"name":"张三","age":25,"isActive":true}

JSON解码(反序列化)

// 将JSON字符串转换为对象
String jsonString = '{"name":"张三","age":25,"isActive":true}';
Map<String, dynamic> data = jsonDecode(jsonString);

print(data['name']); // 张三
print(data['age']); // 25

3.3 复杂对象的序列化

对于复杂的业务对象,需要手动实现序列化方法:

class Transaction {
  final String id;
  final String title;
  final double amount;
  final DateTime date;

  Transaction({
    required this.id,
    required this.title,
    required this.amount,
    required this.date,
  });

  // 序列化方法
  Map<String, dynamic> toJson() {
    return {
      'id': id,
      'title': title,
      'amount': amount,
      'date': date.millisecondsSinceEpoch, // DateTime转时间戳
    };
  }

  // 反序列化工厂方法
  factory Transaction.fromJson(Map<String, dynamic> json) {
    return Transaction(
      id: json['id'] as String,
      title: json['title'] as String,
      amount: json['amount'] as double,
      date: DateTime.fromMillisecondsSinceEpoch(json['date'] as int),
    );
  }
}

// 使用示例
Transaction transaction = Transaction(
  id: '1',
  title: '午餐',
  amount: 35.50,
  date: DateTime.now(),
);

// 序列化
String jsonString = jsonEncode(transaction.toJson());

// 反序列化
Map<String, dynamic> json = jsonDecode(jsonString);
Transaction restored = Transaction.fromJson(json);

3.4 列表数据的序列化

// 对象列表
List<Transaction> transactions = [
  Transaction(id: '1', title: '午餐', amount: 35.50, date: DateTime.now()),
  Transaction(id: '2', title: '交通', amount: 5.00, date: DateTime.now()),
];

// 序列化为JSON字符串列表
List<String> jsonList = transactions.map((t) => jsonEncode(t.toJson())).toList();

// 保存到SharedPreferences
await prefs.setStringList('transactions', jsonList);

// 从SharedPreferences读取
List<String>? savedJsonList = prefs.getStringList('transactions');

// 反序列化为对象列表
List<Transaction> restoredTransactions = savedJsonList?.map((json) {
  return Transaction.fromJson(jsonDecode(json) as Map<String, dynamic>);
}).toList() ?? [];

四、数据模型设计最佳实践

4.1 不可变对象设计

使用final关键字确保对象不可变:

class Transaction {
  final String id;        // 不可变ID
  final String title;     // 不可变标题
  final double amount;    // 不可变金额
  final DateTime date;    // 不可变日期

  Transaction({
    required this.id,
    required this.title,
    required this.amount,
    required this.date,
  });
}

不可变对象的优势

  • 线程安全
  • 防止意外修改
  • 便于状态管理
  • 支持值比较

4.2 命名构造函数

提供便捷的构造方法:

class Transaction {
  final String id;
  final String title;
  final double amount;
  final DateTime date;

  // 默认构造函数
  Transaction({
    required this.id,
    required this.title,
    required this.amount,
    required this.date,
  });

  // 命名构造函数:创建当前时间的新记录
  Transaction.create({
    required this.title,
    required this.amount,
  })  : id = DateTime.now().millisecondsSinceEpoch.toString(),
        date = DateTime.now();

  // 命名构造函数:从JSON创建
  factory Transaction.fromJson(Map<String, dynamic> json) {
    return Transaction(
      id: json['id'] as String,
      title: json['title'] as String,
      amount: json['amount'] as double,
      date: DateTime.fromMillisecondsSinceEpoch(json['date'] as int),
    );
  }
}

4.3 类型安全处理

使用as关键字进行类型转换:

factory Transaction.fromJson(Map<String, dynamic> json) {
  return Transaction(
    id: json['id'] as String,              // 明确类型转换
    title: json['title'] as String,
    amount: (json['amount'] as num).toDouble(), // 处理int到double的转换
    date: DateTime.fromMillisecondsSinceEpoch(json['date'] as int),
  );
}

使用空值合并运算符提供默认值

String id = json['id'] as String? ?? '';
double amount = (json['amount'] as num?)?.toDouble() ?? 0.0;

4.4 数据验证

在fromJson中添加数据验证:

factory Transaction.fromJson(Map<String, dynamic> json) {
  // 验证必需字段
  if (!json.containsKey('id') || json['id'] == null) {
    throw ArgumentError('缺少必需字段: id');
  }

  // 验证数据类型
  final amount = json['amount'];
  if (amount is! num) {
    throw ArgumentError('amount必须是数字类型');
  }

  return Transaction(
    id: json['id'] as String,
    title: json['title'] as String? ?? '',
    amount: amount.toDouble(),
    date: json['date'] != null
        ? DateTime.fromMillisecondsSinceEpoch(json['date'] as int)
        : DateTime.now(),
  );
}

五、实战:实现记账应用数据持久化

5.1 数据加载实现

Future<void> _loadData() async {
  try {
    // 获取SharedPreferences实例
    final prefs = await SharedPreferences.getInstance();

    // 读取保存的JSON字符串列表
    final transactionsJson = prefs.getStringList('transactions') ?? [];

    // 反序列化为Transaction对象列表
    setState(() {
      _transactions = transactionsJson.map((json) {
        return Transaction.fromJson(
          jsonDecode(json) as Map<String, dynamic>
        );
      }).toList();

      // 重新计算统计
      _calculateTotals();
    });
  } catch (e) {
    // 错误处理
    print('加载数据失败: $e');
    setState(() {
      _transactions = [];
      _calculateTotals();
    });
  }
}

加载流程解析

在这里插入图片描述

5.2 数据保存实现

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

    // 将Transaction列表序列化为JSON字符串列表
    final transactionsJson = _transactions.map((t) {
      return jsonEncode(t.toJson());
    }).toList();

    // 保存到本地
    await prefs.setStringList('transactions', transactionsJson);

    print('数据保存成功,共${_transactions.length}条记录');
  } catch (e) {
    print('保存数据失败: $e');
  }
}

保存流程解析

在这里插入图片描述

5.3 添加记录的数据保存

void _addTransaction(Transaction transaction) {
  setState(() {
    // 添加到内存列表
    _transactions.add(transaction);

    // 重新计算统计
    _calculateTotals();
  });

  // 持久化到本地
  _saveData();
}

5.4 删除记录的数据更新

void _deleteTransaction(String id) {
  setState(() {
    // 从内存列表中移除
    _transactions.removeWhere((t) => t.id == id);

    // 重新计算统计
    _calculateTotals();
  });

  // 更新持久化数据
  _saveData();
}

5.5 初始化加载

在initState中加载数据:


void initState() {
  super.initState();
  _loadData();
}

六、性能优化与错误处理

6.1 性能优化策略

批量操作

// 不好的做法:每次添加都保存
void addTransactionBad(Transaction transaction) {
  _transactions.add(transaction);
  _saveData(); // 频繁的IO操作
}

// 好的做法:批量操作后统一保存
void addTransactionsGood(List<Transaction> transactions) {
  setState(() {
    _transactions.addAll(transactions);
    _calculateTotals();
  });
  _saveData(); // 只保存一次
}

避免过度序列化

// 不好的做法:每次都重新序列化整个列表
Widget build(BuildContext context) {
  final jsonList = _transactions.map((t) => jsonEncode(t.toJson())).toList();
  return Text('${jsonList.length}');
}

// 好的做法:使用已加载的数据
Widget build(BuildContext context) {
  return Text('共${_transactions.length}条记录');
}

6.2 错误处理机制

完整的错误处理

Future<void> _loadData() async {
  try {
    final prefs = await SharedPreferences.getInstance();
    final transactionsJson = prefs.getStringList('transactions') ?? [];

    setState(() {
      _transactions = transactionsJson.map((json) {
        try {
          return Transaction.fromJson(
            jsonDecode(json) as Map<String, dynamic>
          );
        } catch (e) {
          // 跳过损坏的数据
          print('解析失败,跳过该记录: $e');
          return null;
        }
      }).whereType<Transaction>().toList();

      _calculateTotals();
    });
  } catch (e) {
    print('加载数据失败: $e');
    setState(() {
      _transactions = [];
      _calculateTotals();
    });
  }
}

6.3 数据版本管理

当数据结构变化时,需要处理版本兼容:

class Transaction {
  final String id;
  final String title;
  final double amount;
  final DateTime date;
  final String? note; // 新增字段

  Transaction({
    required this.id,
    required this.title,
    required this.amount,
    required this.date,
    this.note, // 可选字段
  });

  factory Transaction.fromJson(Map<String, dynamic> json) {
    return Transaction(
      id: json['id'] as String,
      title: json['title'] as String,
      amount: (json['amount'] as num).toDouble(),
      date: DateTime.fromMillisecondsSinceEpoch(json['date'] as int),
      // 处理新字段的兼容性
      note: json['note'] as String?, // 新版本有此字段
    );
  }
}

七、总结

本文深入讲解了Flutter for OpenHarmony平台上的数据持久化方案,主要内容包括:

  1. SharedPreferences:掌握轻量级键值对存储的使用方法
  2. JSON序列化:理解dart:convert的使用技巧
  3. 数据模型设计:学会设计类型安全的数据模型类
  4. 实战应用:实现完整的记账应用数据持久化
  5. 性能优化:了解批量操作和错误处理策略

数据持久化是移动应用的基础能力,掌握这些技术可以让你的应用具备真正的实用价值。通过不断实践,读者可以根据应用需求选择合适的存储方案,构建出功能完善、性能优异的应用。


欢迎加入开源鸿蒙跨平台社区: 开源鸿蒙跨平台开发者社区

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐