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方法:
数据库帮助类
Section titled “数据库帮助类”创建一个单例类(例如,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 中使用数据库帮助类
Section titled “在 UI 中使用数据库帮助类”在你的 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 { // ... 你的应用设置 ...}Firestore 操作
Section titled “Firestore 操作”使用 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(); }}在 UI 中使用 Firestore 流
Section titled “在 UI 中使用 Firestore 流”使用 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), ), ); }}其他持久化选项
Section titled “其他持久化选项”除了 sqflite 和 cloud_firestore,还可以考虑以下选项:
shared_preferences: 用于存储简单的键值对(设置、标志)。非常轻量。hive: 一个快速、原生的键值数据库,用 Dart 编写。比shared_preferences更适合存储稍微复杂一些的本地数据。drift(以前称为 Moor): 一个基于sqflite或sql.js构建的响应式持久化库,提供编译时安全和生成的查询代码。isar: 一个非常快速的跨平台数据库,专注于性能和易用性。
选择合适的持久化方法取决于你的数据复杂性、是否需要离线访问、实时更新或关系型查询功能。
进一步阅读:查看 sqflite、cloud_firestore 和其他持久化 packages 在 pub.dev 上的官方文档,以获取更详细的示例和高级功能。