第9章:数据持久化方案
9.1 数据持久化概述
在移动应用开发中,数据持久化就像是给应用装上了"记忆"。想象一下,如果每次打开微信都要重新登录,所有聊天记录都消失了,这样的应用还有什么价值呢?数据持久化让我们的应用能够"记住"用户的设置、保存重要数据,即使应用关闭重启,这些信息依然存在。
Flutter提供了多种数据持久化方案,每种方案都有其适用场景:
- SharedPreferences:适合存储简单的配置信息,如用户偏好设置
- 文件存储:适合存储文档、图片等文件数据
- SQLite:适合存储结构化的关系型数据
- Hive:轻量级、高性能的NoSQL数据库
- ObjectBox:面向对象的高性能数据库
让我们深入了解每种方案的特点和使用方法。
9.2 SharedPreferences:轻量级存储的首选
9.2.1 什么是SharedPreferences
SharedPreferences就像是应用的"便签本",专门用来记录一些简单的配置信息。比如用户是否第一次打开应用、主题颜色偏好、语言设置等。它的特点是使用简单、读写快速,但只能存储基本数据类型。
9.2.2 基础使用
首先在pubspec.yaml中添加依赖:
dependencies:
shared_preferences: ^2.2.2
让我们通过一个实际例子来学习:
import 'package:shared_preferences/shared_preferences.dart';
class UserPreferences {
static SharedPreferences? _preferences;
// 初始化
static Future<void> init() async {
_preferences = await SharedPreferences.getInstance();
}
// 保存用户名
static Future<void> setUsername(String username) async {
await _preferences?.setString('username', username);
}
// 获取用户名
static String getUsername() {
return _preferences?.getString('username') ?? '';
}
// 保存是否首次启动
static Future<void> setFirstLaunch(bool isFirst) async {
await _preferences?.setBool('first_launch', isFirst);
}
// 检查是否首次启动
static bool isFirstLaunch() {
return _preferences?.getBool('first_launch') ?? true;
}
// 保存主题模式
static Future<void> setThemeMode(int mode) async {
await _preferences?.setInt('theme_mode', mode);
}
// 获取主题模式
static int getThemeMode() {
return _preferences?.getInt('theme_mode') ?? 0;
}
}
9.2.3 实战案例:用户设置管理
让我们创建一个完整的用户设置管理系统:
class SettingsScreen extends StatefulWidget {
_SettingsScreenState createState() => _SettingsScreenState();
}
class _SettingsScreenState extends State<SettingsScreen> {
bool _notificationsEnabled = true;
bool _darkModeEnabled = false;
String _language = 'zh-CN';
void initState() {
super.initState();
_loadSettings();
}
// 加载设置
void _loadSettings() async {
final prefs = await SharedPreferences.getInstance();
setState(() {
_notificationsEnabled = prefs.getBool('notifications') ?? true;
_darkModeEnabled = prefs.getBool('dark_mode') ?? false;
_language = prefs.getString('language') ?? 'zh-CN';
});
}
// 保存设置
void _saveSetting(String key, dynamic value) async {
final prefs = await SharedPreferences.getInstance();
if (value is bool) {
await prefs.setBool(key, value);
} else if (value is String) {
await prefs.setString(key, value);
}
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('设置')),
body: ListView(
children: [
SwitchListTile(
title: Text('推送通知'),
subtitle: Text('接收应用通知'),
value: _notificationsEnabled,
onChanged: (value) {
setState(() {
_notificationsEnabled = value;
});
_saveSetting('notifications', value);
},
),
SwitchListTile(
title: Text('深色模式'),
subtitle: Text('使用深色主题'),
value: _darkModeEnabled,
onChanged: (value) {
setState(() {
_darkModeEnabled = value;
});
_saveSetting('dark_mode', value);
},
),
ListTile(
title: Text('语言设置'),
subtitle: Text(_language == 'zh-CN' ? '简体中文' : 'English'),
trailing: Icon(Icons.arrow_forward_ios),
onTap: () {
// 显示语言选择对话框
_showLanguageDialog();
},
),
],
),
);
}
void _showLanguageDialog() {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('选择语言'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
RadioListTile<String>(
title: Text('简体中文'),
value: 'zh-CN',
groupValue: _language,
onChanged: (value) {
setState(() {
_language = value!;
});
_saveSetting('language', value!);
Navigator.pop(context);
},
),
RadioListTile<String>(
title: Text('English'),
value: 'en-US',
groupValue: _language,
onChanged: (value) {
setState(() {
_language = value!;
});
_saveSetting('language', value!);
Navigator.pop(context);
},
),
],
),
),
);
}
}
9.3 文件系统:处理复杂数据的利器
9.3.1 文件存储的应用场景
文件存储适合保存以下类型的数据:
- 用户生成的文档(如笔记、草稿)
- 下载的图片、音频、视频文件
- 导出的数据文件(CSV、JSON等)
- 缓存数据
9.3.2 获取存储路径
Flutter通过path_provider插件提供了多个存储目录:
dependencies:
path_provider: ^2.1.1
import 'package:path_provider/path_provider.dart';
import 'dart:io';
class FileStorageHelper {
// 获取应用文档目录(私有,用户不可见)
static Future<String> getDocumentsPath() async {
final directory = await getApplicationDocumentsDirectory();
return directory.path;
}
// 获取临时目录(系统可能清理)
static Future<String> getTempPath() async {
final directory = await getTemporaryDirectory();
return directory.path;
}
// 获取外部存储目录(Android,用户可见)
static Future<String?> getExternalStoragePath() async {
final directory = await getExternalStorageDirectory();
return directory?.path;
}
// 获取应用支持目录(存放应用数据)
static Future<String> getSupportPath() async {
final directory = await getApplicationSupportDirectory();
return directory.path;
}
}
9.3.3 文件读写操作
让我们创建一个通用的文件管理类:
class FileManager {
// 写入文本文件
static Future<bool> writeTextFile(String fileName, String content) async {
try {
final path = await FileStorageHelper.getDocumentsPath();
final file = File('$path/$fileName');
await file.writeAsString(content);
return true;
} catch (e) {
print('写入文件失败: $e');
return false;
}
}
// 读取文本文件
static Future<String?> readTextFile(String fileName) async {
try {
final path = await FileStorageHelper.getDocumentsPath();
final file = File('$path/$fileName');
if (await file.exists()) {
return await file.readAsString();
}
return null;
} catch (e) {
print('读取文件失败: $e');
return null;
}
}
// 写入二进制文件
static Future<bool> writeBinaryFile(String fileName, List<int> bytes) async {
try {
final path = await FileStorageHelper.getDocumentsPath();
final file = File('$path/$fileName');
await file.writeAsBytes(bytes);
return true;
} catch (e) {
print('写入二进制文件失败: $e');
return false;
}
}
// 删除文件
static Future<bool> deleteFile(String fileName) async {
try {
final path = await FileStorageHelper.getDocumentsPath();
final file = File('$path/$fileName');
if (await file.exists()) {
await file.delete();
return true;
}
return false;
} catch (e) {
print('删除文件失败: $e');
return false;
}
}
// 检查文件是否存在
static Future<bool> fileExists(String fileName) async {
try {
final path = await FileStorageHelper.getDocumentsPath();
final file = File('$path/$fileName');
return await file.exists();
} catch (e) {
return false;
}
}
// 获取文件大小
static Future<int> getFileSize(String fileName) async {
try {
final path = await FileStorageHelper.getDocumentsPath();
final file = File('$path/$fileName');
if (await file.exists()) {
return await file.length();
}
return 0;
} catch (e) {
return 0;
}
}
// 列出目录中的所有文件
static Future<List<String>> listFiles() async {
try {
final path = await FileStorageHelper.getDocumentsPath();
final directory = Directory(path);
final files = await directory.list().toList();
return files
.where((entity) => entity is File)
.map((file) => file.path.split('/').last)
.toList();
} catch (e) {
print('列出文件失败: $e');
return [];
}
}
}
9.3.4 实战案例:笔记本应用
让我们创建一个简单的笔记本应用:
class Note {
final String id;
final String title;
final String content;
final DateTime createdAt;
final DateTime updatedAt;
Note({
required this.id,
required this.title,
required this.content,
required this.createdAt,
required this.updatedAt,
});
Map<String, dynamic> toJson() {
return {
'id': id,
'title': title,
'content': content,
'createdAt': createdAt.toIso8601String(),
'updatedAt': updatedAt.toIso8601String(),
};
}
factory Note.fromJson(Map<String, dynamic> json) {
return Note(
id: json['id'],
title: json['title'],
content: json['content'],
createdAt: DateTime.parse(json['createdAt']),
updatedAt: DateTime.parse(json['updatedAt']),
);
}
}
class NotesManager {
static const String _notesFileName = 'notes.json';
// 保存所有笔记
static Future<bool> saveNotes(List<Note> notes) async {
try {
final jsonList = notes.map((note) => note.toJson()).toList();
final jsonString = jsonEncode(jsonList);
return await FileManager.writeTextFile(_notesFileName, jsonString);
} catch (e) {
print('保存笔记失败: $e');
return false;
}
}
// 加载所有笔记
static Future<List<Note>> loadNotes() async {
try {
final jsonString = await FileManager.readTextFile(_notesFileName);
if (jsonString != null) {
final List<dynamic> jsonList = jsonDecode(jsonString);
return jsonList.map((json) => Note.fromJson(json)).toList();
}
return [];
} catch (e) {
print('加载笔记失败: $e');
return [];
}
}
// 添加笔记
static Future<bool> addNote(Note note) async {
final notes = await loadNotes();
notes.add(note);
return await saveNotes(notes);
}
// 更新笔记
static Future<bool> updateNote(Note updatedNote) async {
final notes = await loadNotes();
final index = notes.indexWhere((note) => note.id == updatedNote.id);
if (index != -1) {
notes[index] = updatedNote;
return await saveNotes(notes);
}
return false;
}
// 删除笔记
static Future<bool> deleteNote(String noteId) async {
final notes = await loadNotes();
notes.removeWhere((note) => note.id == noteId);
return await saveNotes(notes);
}
}
9.4 SQLite:结构化数据的专业选择
9.4.1 SQLite简介
SQLite是一个轻量级的关系型数据库,特别适合移动应用。它支持SQL查询、事务处理、索引等高级功能,是处理复杂关系数据的首选方案。
9.4.2 集成SQLite
首先添加依赖:
dependencies:
sqflite: ^2.3.0
9.4.3 数据库设计与创建
让我们创建一个任务管理应用的数据库:
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
class DatabaseHelper {
static Database? _database;
static const String _databaseName = 'task_manager.db';
static const int _databaseVersion = 1;
// 单例模式
static DatabaseHelper? _instance;
DatabaseHelper._();
static DatabaseHelper get instance {
_instance ??= DatabaseHelper._();
return _instance!;
}
// 获取数据库实例
Future<Database> get database async {
_database ??= await _initDatabase();
return _database!;
}
// 初始化数据库
Future<Database> _initDatabase() async {
String path = join(await getDatabasesPath(), _databaseName);
return await openDatabase(
path,
version: _databaseVersion,
onCreate: _onCreate,
onUpgrade: _onUpgrade,
);
}
// 创建表
Future<void> _onCreate(Database db, int version) async {
// 用户表
await db.execute('''
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL UNIQUE,
email TEXT NOT NULL,
created_at TEXT NOT NULL
)
''');
// 分类表
await db.execute('''
CREATE TABLE categories (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
color INTEGER NOT NULL,
created_at TEXT NOT NULL
)
''');
// 任务表
await db.execute('''
CREATE TABLE tasks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
description TEXT,
category_id INTEGER,
priority INTEGER NOT NULL DEFAULT 1,
is_completed INTEGER NOT NULL DEFAULT 0,
due_date TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
FOREIGN KEY (category_id) REFERENCES categories (id)
)
''');
// 创建索引
await db.execute('''
CREATE INDEX idx_tasks_category ON tasks(category_id)
''');
await db.execute('''
CREATE INDEX idx_tasks_completed ON tasks(is_completed)
''');
}
// 数据库升级
Future<void> _onUpgrade(Database db, int oldVersion, int newVersion) async {
if (oldVersion < 2) {
// 假设版本2添加了新字段
await db.execute('''
ALTER TABLE tasks ADD COLUMN reminder_time TEXT
''');
}
}
}
9.4.4 数据模型定义
定义数据模型类:
class Task {
final int? id;
final String title;
final String? description;
final int? categoryId;
final int priority;
final bool isCompleted;
final DateTime? dueDate;
final DateTime createdAt;
final DateTime updatedAt;
Task({
this.id,
required this.title,
this.description,
this.categoryId,
this.priority = 1,
this.isCompleted = false,
this.dueDate,
required this.createdAt,
required this.updatedAt,
});
Map<String, dynamic> toMap() {
return {
'id': id,
'title': title,
'description': description,
'category_id': categoryId,
'priority': priority,
'is_completed': isCompleted ? 1 : 0,
'due_date': dueDate?.toIso8601String(),
'created_at': createdAt.toIso8601String(),
'updated_at': updatedAt.toIso8601String(),
};
}
factory Task.fromMap(Map<String, dynamic> map) {
return Task(
id: map['id'],
title: map['title'],
description: map['description'],
categoryId: map['category_id'],
priority: map['priority'],
isCompleted: map['is_completed'] == 1,
dueDate: map['due_date'] != null ? DateTime.parse(map['due_date']) : null,
createdAt: DateTime.parse(map['created_at']),
updatedAt: DateTime.parse(map['updated_at']),
);
}
Task copyWith({
int? id,
String? title,
String? description,
int? categoryId,
int? priority,
bool? isCompleted,
DateTime? dueDate,
DateTime? createdAt,
DateTime? updatedAt,
}) {
return Task(
id: id ?? this.id,
title: title ?? this.title,
description: description ?? this.description,
categoryId: categoryId ?? this.categoryId,
priority: priority ?? this.priority,
isCompleted: isCompleted ?? this.isCompleted,
dueDate: dueDate ?? this.dueDate,
createdAt: createdAt ?? this.createdAt,
updatedAt: updatedAt ?? this.updatedAt,
);
}
}
class Category {
final int? id;
final String name;
final int color;
final DateTime createdAt;
Category({
this.id,
required this.name,
required this.color,
required this.createdAt,
});
Map<String, dynamic> toMap() {
return {
'id': id,
'name': name,
'color': color,
'created_at': createdAt.toIso8601String(),
};
}
factory Category.fromMap(Map<String, dynamic> map) {
return Category(
id: map['id'],
name: map['name'],
color: map['color'],
createdAt: DateTime.parse(map['created_at']),
);
}
}
9.4.5 数据访问层(DAO)
创建数据访问对象来处理数据库操作:
class TaskDao {
static Future<Database> get _db async => await DatabaseHelper.instance.database;
// 插入任务
static Future<int> insertTask(Task task) async {
final db = await _db;
return await db.insert('tasks', task.toMap());
}
// 获取所有任务
static Future<List<Task>> getAllTasks() async {
final db = await _db;
final List<Map<String, dynamic>> maps = await db.query(
'tasks',
orderBy: 'created_at DESC',
);
return List.generate(maps.length, (i) => Task.fromMap(maps[i]));
}
// 根据ID获取任务
static Future<Task?> getTaskById(int id) async {
final db = await _db;
final List<Map<String, dynamic>> maps = await db.query(
'tasks',
where: 'id = ?',
whereArgs: [id],
);
if (maps.isNotEmpty) {
return Task.fromMap(maps.first);
}
return null;
}
// 根据分类获取任务
static Future<List<Task>> getTasksByCategory(int categoryId) async {
final db = await _db;
final List<Map<String, dynamic>> maps = await db.query(
'tasks',
where: 'category_id = ?',
whereArgs: [categoryId],
orderBy: 'created_at DESC',
);
return List.generate(maps.length, (i) => Task.fromMap(maps[i]));
}
// 获取未完成的任务
static Future<List<Task>> getIncompleteTasks() async {
final db = await _db;
final List<Map<String, dynamic>> maps = await db.query(
'tasks',
where: 'is_completed = ?',
whereArgs: [0],
orderBy: 'due_date ASC',
);
return List.generate(maps.length, (i) => Task.fromMap(maps[i]));
}
// 更新任务
static Future<int> updateTask(Task task) async {
final db = await _db;
return await db.update(
'tasks',
task.toMap(),
where: 'id = ?',
whereArgs: [task.id],
);
}
// 标记任务为完成
static Future<int> markTaskCompleted(int id) async {
final db = await _db;
return await db.update(
'tasks',
{
'is_completed': 1,
'updated_at': DateTime.now().toIso8601String(),
},
where: 'id = ?',
whereArgs: [id],
);
}
// 删除任务
static Future<int> deleteTask(int id) async {
final db = await _db;
return await db.delete(
'tasks',
where: 'id = ?',
whereArgs: [id],
);
}
// 搜索任务
static Future<List<Task>> searchTasks(String keyword) async {
final db = await _db;
final List<Map<String, dynamic>> maps = await db.query(
'tasks',
where: 'title LIKE ? OR description LIKE ?',
whereArgs: ['%$keyword%', '%$keyword%'],
orderBy: 'created_at DESC',
);
return List.generate(maps.length, (i) => Task.fromMap(maps[i]));
}
// 获取任务统计信息
static Future<Map<String, int>> getTaskStats() async {
final db = await _db;
final totalResult = await db.rawQuery('SELECT COUNT(*) as count FROM tasks');
final completedResult = await db.rawQuery('SELECT COUNT(*) as count FROM tasks WHERE is_completed = 1');
final pendingResult = await db.rawQuery('SELECT COUNT(*) as count FROM tasks WHERE is_completed = 0');
return {
'total': totalResult.first['count'] as int,
'completed': completedResult.first['count'] as int,
'pending': pendingResult.first['count'] as int,
};
}
}
class CategoryDao {
static Future<Database> get _db async => await DatabaseHelper.instance.database;
// 插入分类
static Future<int> insertCategory(Category category) async {
final db = await _db;
return await db.insert('categories', category.toMap());
}
// 获取所有分类
static Future<List<Category>> getAllCategories() async {
final db = await _db;
final List<Map<String, dynamic>> maps = await db.query(
'categories',
orderBy: 'name ASC',
);
return List.generate(maps.length, (i) => Category.fromMap(maps[i]));
}
// 更新分类
static Future<int> updateCategory(Category category) async {
final db = await _db;
return await db.update(
'categories',
category.toMap(),
where: 'id = ?',
whereArgs: [category.id],
);
}
// 删除分类
static Future<int> deleteCategory(int id) async {
final db = await _db;
// 首先检查是否有任务使用这个分类
final taskCount = await db.rawQuery(
'SELECT COUNT(*) as count FROM tasks WHERE category_id = ?',
[id],
);
if ((taskCount.first['count'] as int) > 0) {
throw Exception('无法删除分类:还有任务正在使用此分类');
}
return await db.delete(
'categories',
where: 'id = ?',
whereArgs: [id],
);
}
}
9.5 Hive:高性能NoSQL数据库
9.5.1 Hive的优势
Hive是一个专为Flutter设计的轻量级、超快速的键值数据库。它的主要优势包括:
- 性能优异:比SQLite快很多倍
- 使用简单:不需要SQL语句
- 类型安全:支持Dart的强类型
- 跨平台:支持所有Flutter平台
- 加密支持:内置数据加密功能
9.5.2 集成Hive
添加依赖:
dependencies:
hive: ^2.2.3
hive_flutter: ^1.1.0
dev_dependencies:
hive_generator: ^2.0.1
build_runner: ^2.4.7
9.5.3 数据模型定义
使用Hive的TypeAdapter来定义数据模型:
import 'package:hive/hive.dart';
part 'user_model.g.dart'; // 生成的代码文件
(typeId: 0)
class User extends HiveObject {
(0)
late String id;
(1)
late String name;
(2)
late String email;
(3)
late DateTime createdAt;
(4)
String? avatarUrl;
(5)
late bool isActive;
User({
required this.id,
required this.name,
required this.email,
required this.createdAt,
this.avatarUrl,
this.isActive = true,
});
String toString() {
return 'User{id: $id, name: $name, email: $email}';
}
}
(typeId: 1)
class Product extends HiveObject {
(0)
late String id;
(1)
late String name;
(2)
late String description;
(3)
late double price;
(4)
late int stock;
(5)
late List<String> images;
(6)
late DateTime createdAt;
(7)
late DateTime updatedAt;
Product({
required this.id,
required this.name,
required this.description,
required this.price,
required this.stock,
required this.images,
required this.createdAt,
required this.updatedAt,
});
}
运行代码生成器:
flutter packages pub run build_runner build
9.5.4 Hive初始化和管理
创建Hive管理类:
import 'package:hive_flutter/hive_flutter.dart';
import 'package:path_provider/path_provider.dart';
class HiveManager {
static bool _isInitialized = false;
// 初始化Hive
static Future<void> initialize() async {
if (_isInitialized) return;
// 初始化Hive
await Hive.initFlutter();
// 注册适配器
Hive.registerAdapter(UserAdapter());
Hive.registerAdapter(ProductAdapter());
_isInitialized = true;
}
// 打开所有需要的Box
static Future<void> openBoxes() async {
await Hive.openBox<User>('users');
await Hive.openBox<Product>('products');
await Hive.openBox('settings'); // 用于存储基本类型
await Hive.openBox('cache');
}
// 关闭所有Box
static Future<void> closeBoxes() async {
await Hive.close();
}
// 清除所有数据
static Future<void> clearAllData() async {
await Hive.box<User>('users').clear();
await Hive.box<Product>('products').clear();
await Hive.box('settings').clear();
await Hive.box('cache').clear();
}
}
9.5.5 数据操作
创建数据仓库类:
class UserRepository {
static Box<User> get _box => Hive.box<User>('users');
// 添加用户
static Future<void> addUser(User user) async {
await _box.put(user.id, user

最低0.47元/天 解锁文章
1885

被折叠的 条评论
为什么被折叠?



