Skip to content

flutter_data_persistence

Flutter - 数据持久化 (SQLite & Cloud Firestore)

Section titled “Flutter - 数据持久化 (SQLite & Cloud Firestore)”

存储和检索数据对于大多数应用程序至关重要。Flutter 提供了多种在本地和远程持久化数据的方式。本章探讨两种流行的选择:用于本地关系数据的 SQLite 和用于基于云的 NoSQL 数据的 Cloud Firestore。

使用 sqflite 进行本地持久化 (SQLite)

Section titled “使用 sqflite 进行本地持久化 (SQLite)”

sqflite 是一个流行的 Flutter 插件,它提供对 Android 和 iOS 原生 SQLite 数据库实现的访问。它允许你执行标准的 SQL 操作。

sqflite 提供的核心功能:

  • 创建或打开数据库 (openDatabase)。
  • 执行原始 SQL 语句 (execute、rawQuery、rawInsert、rawUpdate、rawDelete)。
  • 方便的 CRUD(创建、读取、更新、删除)操作方法 (query、insert、update、delete)。
  • 批处理操作,提高效率。
  • 在 onCreate、onUpgrade、onDowngrade 期间支持数据库版本控制和迁移。

让我们使用 sqflite 构建一个简单的产品存储应用程序。

  • 创建一个新的 Flutter 项目:flutter create product_sqflite_app

  • 在 pubspec.yaml 中添加依赖项。使用 pub.dev 上的最新版本:

  • 运行 flutter pub get 安装 packages。

  • 创建一个 Product 模型类(例如,在 lib/product.dart 中),并包含用于序列化的 toMap 和 fromMap 方法:

创建一个单例类(例如,lib/database_helper.dart)来管理数据库操作:

import 'dart:async';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
import 'product.dart'; // 导入 Product 模型
class DatabaseHelper {
static final DatabaseHelper _instance = DatabaseHelper._internal();
factory DatabaseHelper() => _instance;
static Database? _database;
DatabaseHelper._internal();
Future<Database> get database async {
if (_database != null) return _database!;
_database = await _initDb();
return _database!;
}
Future<Database> _initDb() async {
String path = join(await getDatabasesPath(), 'product_database.db');
return await openDatabase(
path,
version: 1,
onCreate: _onCreate,
);
}
Future<void> _onCreate(Database db, int version) async {
await db.execute('''
CREATE TABLE products (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
description TEXT NOT NULL,
price REAL NOT NULL,
image TEXT NOT NULL
)
''');
// 可选:插入初始数据
await _insertInitialData(db);
}
Future<void> _insertInitialData(Database db) async {
// 如果插入多个项目,使用 batch 提高效率
Batch batch = db.batch();
batch.insert('products', {'name': 'Laptop', 'description': 'High-performance laptop', 'price': 1200.0, 'image': 'laptop.png'});
batch.insert('products', {'name': 'Smartphone', 'description': 'Latest model smartphone', 'price': 800.0, 'image': 'phone.png'});
await batch.commit(noResult: true);
}
// 插入 Product
Future<int> insertProduct(Product product) async {
final db = await database;
// 使用 toMap,排除 id,因为它会自动增长
Map<String, dynamic> row = product.toMap();
row.remove('id'); // 让 SQLite 处理 ID
return await db.insert('products', row, conflictAlgorithm: ConflictAlgorithm.replace);
}
// 获取所有 Product
Future<List<Product>> getAllProducts() async {
final db = await database;
final List<Map<String, dynamic>> maps = await db.query('products', orderBy: 'name ASC');
return List.generate(maps.length, (i) {
return Product.fromMap(maps[i]);
});
}
// 按 ID 获取单个 Product
Future<Product?> getProductById(int id) async {
final db = await database;
List<Map<String, dynamic>> maps = await db.query(
'products',
where: 'id = ?',
whereArgs: [id],
);
if (maps.isNotEmpty) {
return Product.fromMap(maps.first);
} else {
return null;
}
}
// 更新 Product
Future<int> updateProduct(Product product) async {
final db = await database;
return await db.update(
'products',
product.toMap(),
where: 'id = ?',
whereArgs: [product.id],
);
}
// 删除 Product
Future<int> deleteProduct(int id) async {
final db = await database;
return await db.delete(
'products',
where: 'id = ?',
whereArgs: [id],
);
}
// 关闭数据库(可选,通常由 sqflite 管理)
Future<void> close() async {
final db = await database;
db.close();
_database = null; // 重置静态变量
}
}

在你的 UI 代码中(例如,在 StatefulWidget 中或使用状态管理解决方案),你可以与数据库交互:

import 'package:flutter/material.dart';
import 'database_helper.dart';
import 'product.dart';
// 在 StatefulWidget 的 State 中的使用示例
class ProductListScreen extends StatefulWidget {
@override
_ProductListScreenState createState() => _ProductListScreenState();
}
class _ProductListScreenState extends State<ProductListScreen> {
late Future<List<Product>> _productsFuture;
final dbHelper = DatabaseHelper();
@override
void initState() {
super.initState();
_refreshProductList();
}
void _refreshProductList() {
setState(() {
_productsFuture = dbHelper.getAllProducts();
});
}
void _addProduct() async {
// 示例:显示对话框或导航到表单以获取产品详情
Product newProduct = Product(
name: 'New Gadget',
description: 'Exciting new gadget',
price: 99.99,
image: 'gadget.png');
await dbHelper.insertProduct(newProduct);
_refreshProductList(); // 更新列表
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('SQLite Products')),
body: FutureBuilder<List<Product>>(
future: _productsFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
} else if (!snapshot.hasData || snapshot.data!.isEmpty) {
return Center(child: Text('No products found.'));
} else {
final products = snapshot.data!;
return ListView.builder(
itemCount: products.length,
itemBuilder: (context, index) {
final product = products[index];
return ListTile(
// 假设图片在 assets/images/ 目录中
// leading: Image.asset('assets/images/${product.image}', width: 50, height: 50, fit: BoxFit.cover),
title: Text(product.name),
subtitle: Text(product.description),
trailing: Text('\$${product.price.toStringAsFixed(2)}'),
onTap: () async {
// 示例:点击删除
if (product.id != null) {
await dbHelper.deleteProduct(product.id!);
_refreshProductList();
}
},
);
},
);
}
},\n ),
floatingActionButton: FloatingActionButton(
onPressed: _addProduct,
child: Icon(Icons.add),
),
);
}
}

此示例演示了基本的设置和 CRUD 操作。实际应用会涉及适当的错误处理、用户输入表单,以及可能更复杂的查询。

使用 Cloud Firestore 进行云端持久化

Section titled “使用 Cloud Firestore 进行云端持久化”

Cloud Firestore 是 Firebase 和 Google Cloud 提供的一个灵活、可扩展的 NoSQL 云数据库。它通过实时监听器使你的数据在客户端应用程序之间保持同步,并提供离线支持。

核心概念:

  • 文档 (Documents): 存储单元,类似于 JSON 对象。
  • 集合 (Collections): 文档的容器。
  • 实时更新 (Realtime Updates): 使用 snapshots() 流来监听数据变化。
  • 离线支持 (Offline Support): 数据在本地缓存以供离线访问。
  • 可扩展性 (Scalability): 专为高可扩展性和性能而设计。
  • 创建 Firebase 项目: 转到 Firebase Console (firebase.google.com),创建一个项目,并向其中添加一个 Android 和/或 iOS 应用。

  • 下载配置文件: 下载 google-services.json (Android) 和 GoogleService-Info.plist (iOS),并将它们放置在 Flutter 项目中的相应目录 (android/app/ 和 ios/Runner/)。

  • 添加 Firebase Core 和 Firestore 依赖项: 在 pubspec.yaml 中添加以下内容(使用最新版本):

  • 配置原生项目: 按照 Firebase 文档提供的针对特定平台的设置说明进行操作(为 Android 的 build.gradle 文件添加插件,可能需要为 iOS 修改 AppDelegate 文件,如果需要则启用 multidex)。此设置可能会随 Flutter/Firebase 的更新而略有变化,因此请始终参考官方文档。

  • 初始化 Firebase: 在你的 main.dart 中,在运行应用之前初始化 Firebase:

import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'firebase_options.dart'; // Generated by FlutterFire CLI or manual setup
void main() async {
// 确保 Flutter 绑定已初始化
WidgetsFlutterBinding.ensureInitialized();
// 初始化 Firebase
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform, // 使用 firebase_options.dart
);
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// ... 你的应用设置 ...
}

使用 cloud_firestore package 与 Firestore 进行交互。假设你有一个 products 集合。

import 'package:cloud_firestore/cloud_firestore.dart';
import 'product.dart'; // 你的 Product 模型(需要 fromMap/toMap 或类似方法)
class FirestoreService {
final FirebaseFirestore _db = FirebaseFirestore.instance;
final CollectionReference _productsCollection = FirebaseFirestore.instance.collection('products');
// 添加新 Product(Firestore 会生成 ID)
Future<DocumentReference> addProduct(Product product) {
// 假设 Product.toMap() 存在并返回 Map<String, dynamic>
return _productsCollection.add(product.toMap()..remove('id')); // Firestore 处理 ID
}
// 获取所有产品的流(实时更新)
Stream<List<Product>> getProductsStream() {
return _productsCollection.snapshots().map((snapshot) {
return snapshot.docs.map((doc) {
// 假设 Product.fromMap 存在
// 在创建对象之前需要将文档 ID 添加到 Map 中
final data = doc.data() as Map<String, dynamic>;
data['id_firestore'] = doc.id; // 如果需要单独存储 Firestore ID,可以这样做
return Product.fromMap(data); // 如有必要,调整 fromMap
}).toList();
});
}
// 获取单个产品(一次性读取)
Future<Product?> getProduct(String docId) async {
DocumentSnapshot doc = await _productsCollection.doc(docId).get();
if (doc.exists) {
final data = doc.data() as Map<String, dynamic>;
data['id_firestore'] = doc.id;
return Product.fromMap(data);
}
return null;
}
// 更新 Product
Future<void> updateProduct(String docId, Product product) {
return _productsCollection.doc(docId).update(product.toMap());
}
// 删除 Product
Future<void> deleteProduct(String docId) {
return _productsCollection.doc(docId).delete();
}
}

使用 StreamBuilder 监听 Firestore 流并自动更新 UI。

import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'firestore_service.dart'; // 你的 Firestore 服务
import 'product.dart';
class FirestoreProductListScreen extends StatelessWidget {
final FirestoreService _firestoreService = FirestoreService();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Firestore Products (Realtime)')),
body: StreamBuilder<List<Product>>(
stream: _firestoreService.getProductsStream(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
print('Firestore Error: ${snapshot.error}'); // 记录错误
return Center(child: Text('Error loading products. See console for details.'));
} else if (!snapshot.hasData || snapshot.data!.isEmpty) {
return Center(child: Text('No products found in Firestore.'));
} else {
final products = snapshot.data!;
return ListView.builder(
itemCount: products.length,
itemBuilder: (context, index) {
final product = products[index];
return ListTile(
title: Text(product.name),
subtitle: Text(product.description),
trailing: Text('\$${product.price.toStringAsFixed(2)}'),
// 添加 onTap/onLongPress 以使用 product.id_firestore 进行更新/删除操作
);
},
);
}
},
),
floatingActionButton: FloatingActionButton(
onPressed: () {
// 示例:添加一个产品
_firestoreService.addProduct(Product(name: 'Cloud Widget', description: 'Syncs magically', price: 199.99, image: 'cloud.png'));
},
child: Icon(Icons.add),
),
);
}
}

除了 sqflite 和 cloud_firestore,还可以考虑以下选项:

  • shared_preferences: 用于存储简单的键值对(设置、标志)。非常轻量。
  • hive: 一个快速、原生的键值数据库,用 Dart 编写。比 shared_preferences 更适合存储稍微复杂一些的本地数据。
  • drift(以前称为 Moor): 一个基于 sqflite 或 sql.js 构建的响应式持久化库,提供编译时安全和生成的查询代码。
  • isar: 一个非常快速的跨平台数据库,专注于性能和易用性。

选择合适的持久化方法取决于你的数据复杂性、是否需要离线访问、实时更新或关系型查询功能。

进一步阅读:查看 sqflite、cloud_firestore 和其他持久化 packages 在 pub.dev 上的官方文档,以获取更详细的示例和高级功能。