Flutter本地SQL数据库(sqflite)与Firestore在线同步实现求助
Flutter 本地sqflite与Firestore双向同步实现方案
以下是完整的实现思路和代码示例,涵盖本地数据库操作、Firestore交互以及联网自动同步逻辑:
1. 依赖配置
在pubspec.yaml中添加所需依赖:
dependencies: flutter: sdk: flutter sqflite: ^2.3.2 path_provider: ^2.1.1 cloud_firestore: ^4.15.5 connectivity_plus: ^5.0.1
2. 数据模型定义
定义统一的数据模型,同时适配本地SQL和Firestore:
class Todo { final String id; final String title; final String content; final bool isSynced; // 标记是否已同步到Firestore final DateTime updatedAt; Todo({ required this.id, required this.title, required this.content, this.isSynced = false, required this.updatedAt, }); // 从SQL查询结果转模型 factory Todo.fromSql(Map<String, dynamic> map) { return Todo( id: map['id'], title: map['title'], content: map['content'], isSynced: map['is_synced'] == 1, updatedAt: DateTime.fromMillisecondsSinceEpoch(map['updated_at']), ); } // 模型转SQL插入/更新数据 Map<String, dynamic> toSql() { return { 'id': id, 'title': title, 'content': content, 'is_synced': isSynced ? 1 : 0, 'updated_at': updatedAt.millisecondsSinceEpoch, }; } // 从Firestore文档转模型 factory Todo.fromFirestore(Map<String, dynamic> data, String documentId) { return Todo( id: documentId, title: data['title'], content: data['content'], isSynced: true, updatedAt: (data['updated_at'] as Timestamp).toDate(), ); } // 模型转Firestore数据 Map<String, dynamic> toFirestore() { return { 'title': title, 'content': content, 'updated_at': Timestamp.fromDate(updatedAt), }; } }
3. 本地SQL数据库操作
创建LocalDatabase类封装sqflite操作:
import 'package:sqflite/sqflite.dart'; import 'package:path_provider/path_provider.dart'; import 'package:path/path.dart'; class LocalDatabase { static Database? _database; static const String tableName = 'todos'; Future<Database> get database async { if (_database != null) return _database!; _database = await _initDb(); return _database!; } // 初始化数据库 Future<Database> _initDb() async { final documentsDir = await getApplicationDocumentsDirectory(); final path = join(documentsDir.path, 'todo_database.db'); return await openDatabase( path, version: 1, onCreate: (db, version) async { await db.execute(''' CREATE TABLE $tableName ( id TEXT PRIMARY KEY, title TEXT NOT NULL, content TEXT, is_synced INTEGER NOT NULL DEFAULT 0, updated_at INTEGER NOT NULL ) '''); }, ); } // 插入或更新本地数据 Future<void> upsertTodo(Todo todo) async { final db = await database; await db.insert( tableName, todo.toSql(), conflictAlgorithm: ConflictAlgorithm.replace, ); } // 获取所有未同步的本地数据 Future<List<Todo>> getUnsyncedTodos() async { final db = await database; final maps = await db.query( tableName, where: 'is_synced = ?', whereArgs: [0], ); return maps.map((map) => Todo.fromSql(map)).toList(); } // 获取所有本地数据 Future<List<Todo>> getAllTodos() async { final db = await database; final maps = await db.query(tableName); return maps.map((map) => Todo.fromSql(map)).toList(); } // 标记数据为已同步 Future<void> markAsSynced(String todoId) async { final db = await database; await db.update( tableName, {'is_synced': 1}, where: 'id = ?', whereArgs: [todoId], ); } // 根据ID删除本地数据 Future<void> deleteTodo(String id) async { final db = await database; await db.delete( tableName, where: 'id = ?', whereArgs: [id], ); } }
4. Firestore操作封装
创建FirestoreService类处理云端数据交互:
import 'package:cloud_firestore/cloud_firestore.dart'; class FirestoreService { final CollectionReference _todosCollection = FirebaseFirestore.instance.collection('todos'); // 上传本地数据到Firestore Future<void> uploadTodo(Todo todo) async { await _todosCollection.doc(todo.id).set(todo.toFirestore()); } // 从Firestore获取所有数据 Future<List<Todo>> getAllRemoteTodos() async { final snapshot = await _todosCollection.get(); return snapshot.docs.map((doc) => Todo.fromFirestore(doc.data() as Map<String, dynamic>, doc.id)).toList(); } // 更新Firestore数据 Future<void> updateTodo(Todo todo) async { await _todosCollection.doc(todo.id).update(todo.toFirestore()); } // 删除Firestore数据 Future<void> deleteTodo(String id) async { await _todosCollection.doc(id).delete(); } }
5. 同步逻辑实现
创建SyncService类处理双向同步,结合网络监听:
import 'package:connectivity_plus/connectivity_plus.dart'; class SyncService { final LocalDatabase localDb; final FirestoreService firestoreService; final Connectivity connectivity = Connectivity(); SyncService({required this.localDb, required this.firestoreService}) { // 监听网络状态变化,联网时触发同步 connectivity.onConnectivityChanged.listen((result) { if (result != ConnectivityResult.none) { syncData(); } }); } // 双向同步主方法 Future<void> syncData() async { // 1. 先将本地未同步数据上传到Firestore await syncLocalToRemote(); // 2. 再将Firestore最新数据同步到本地 await syncRemoteToLocal(); } // 本地→云端同步 Future<void> syncLocalToRemote() async { final unsyncedTodos = await localDb.getUnsyncedTodos(); for (var todo in unsyncedTodos) { await firestoreService.uploadTodo(todo); await localDb.markAsSynced(todo.id); } } // 云端→本地同步(处理冲突:保留更新时间更近的数据) Future<void> syncRemoteToLocal() async { final remoteTodos = await firestoreService.getAllRemoteTodos(); final localTodos = await localDb.getAllTodos(); // 构建本地数据ID映射 final localTodoMap = {for (var t in localTodos) t.id: t}; for (var remoteTodo in remoteTodos) { final localTodo = localTodoMap[remoteTodo.id]; if (localTodo == null) { // 本地不存在,插入 await localDb.upsertTodo(remoteTodo); } else { // 存在则比较更新时间,保留最新的 if (remoteTodo.updatedAt.isAfter(localTodo.updatedAt)) { await localDb.upsertTodo(remoteTodo); } else if (!localTodo.isSynced) { // 本地更新更近且未同步,重新上传 await firestoreService.updateTodo(localTodo); await localDb.markAsSynced(localTodo.id); } } } // 删除本地存在但云端已删除的数据 final remoteIds = remoteTodos.map((t) => t.id).toSet(); for (var localTodo in localTodos) { if (!remoteIds.contains(localTodo.id)) { await localDb.deleteTodo(localTodo.id); } } } }
6. 页面中使用示例
在主页面初始化服务并测试:
import 'package:flutter/material.dart'; import 'package:uuid/uuid.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Local-Firestore Sync', home: const TodoPage(), ); } } class TodoPage extends StatefulWidget { const TodoPage({super.key}); @override State<TodoPage> createState() => _TodoPageState(); } class _TodoPageState extends State<TodoPage> { late final LocalDatabase _localDb; late final FirestoreService _firestoreService; late final SyncService _syncService; List<Todo> _todos = []; final _titleController = TextEditingController(); final _contentController = TextEditingController(); @override void initState() { super.initState(); _localDb = LocalDatabase(); _firestoreService = FirestoreService(); _syncService = SyncService(localDb: _localDb, firestoreService: _firestoreService); _loadLocalTodos(); // 初始检查网络,若联网则同步 _checkNetworkAndSync(); } Future<void> _loadLocalTodos() async { final todos = await _localDb.getAllTodos(); setState(() => _todos = todos); } Future<void> _checkNetworkAndSync() async { final result = await Connectivity().checkConnectivity(); if (result != ConnectivityResult.none) { await _syncService.syncData(); _loadLocalTodos(); } } Future<void> _addTodo() async { final todo = Todo( id: const Uuid().v4(), title: _titleController.text, content: _contentController.text, isSynced: false, updatedAt: DateTime.now(), ); await _localDb.upsertTodo(todo); setState(() => _todos.add(todo)); _titleController.clear(); _contentController.clear(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('待办列表')), body: Column( children: [ Padding( padding: const EdgeInsets.all(16.0), child: Column( children: [ TextField( controller: _titleController, decoration: const InputDecoration(labelText: '标题'), ), TextField( controller: _contentController, decoration: const InputDecoration(labelText: '内容'), ), ElevatedButton(onPressed: _addTodo, child: const Text('添加待办')), ], ), ), Expanded( child: ListView.builder( itemCount: _todos.length, itemBuilder: (context, index) { final todo = _todos[index]; return ListTile( title: Text(todo.title), subtitle: Text(todo.content), trailing: todo.isSynced ? const Icon(Icons.cloud_done, color: Colors.green) : const Icon(Icons.cloud_upload_outlined, color: Colors.grey), ); }, ), ), ], ), ); } }
关键注意事项
- 冲突处理:示例中通过
updatedAt时间戳判断数据版本,实际项目可根据需求调整冲突策略(如用户确认、字段合并等) - 网络监听:使用
connectivity_plus实时监听网络状态,确保联网时自动触发同步 - 数据唯一性:使用UUID确保本地和云端数据ID一致,避免重复
- 错误处理:示例省略了try-catch,实际开发中需添加异常捕获,处理网络请求失败、数据库操作失败等情况
内容的提问来源于stack exchange,提问作者iamshyley
相关产品推荐
相关产品推荐

