Flutter/Bloc应用:恢复网络后Hive本地数据同步至远程API的最佳实践
Flutter/Bloc架构下离线数据同步的最佳实践
针对你遇到的本地Hive数据向远程API同步的场景,把逻辑放在initState里存在局限性(比如页面销毁后无法触发、仅在页面初始化时执行一次),以下是更可靠的实现方案:
1. 用全局Cubit/Bloc统一管理同步逻辑
将同步逻辑从UI层剥离,放到全局的OfflineSyncCubit中(通过依赖注入如get_it提供实例),在应用启动时初始化,同时监听网络状态变化,确保网络恢复时自动触发同步,而非仅依赖页面初始化。
示例代码:
class OfflineSyncCubit extends Cubit<SyncState> { final HiveInterface hive; final ApiService apiService; final Connectivity connectivity; StreamSubscription? _connectivitySubscription; OfflineSyncCubit({ required this.hive, required this.apiService, required this.connectivity, }) : super(SyncInitial()) { _initSync(); } void _initSync() async { // 应用启动时先检查网络,有网则立即同步 final status = await connectivity.checkConnectivity(); if (status != ConnectivityResult.none) { await _syncLocalTasks(); } // 监听网络状态切换,从无网变有网时触发同步 _connectivitySubscription = connectivity.onConnectivityChanged.listen((status) { if (status != ConnectivityResult.none) { _syncLocalTasks(); } }); } Future<void> _syncLocalTasks() async { emit(SyncLoading()); try { final taskBox = await hive.openBox('offline_tasks'); final tasks = taskBox.values.toList().cast<OfflineTask>(); for (final task in tasks) { // 发送请求(需保证接口幂等,用task.id作为唯一标识) await apiService.submitTask(task); // 同步成功后删除本地任务 await taskBox.delete(task.id); } emit(SyncSuccess()); } catch (e) { emit(SyncFailure(error: e.toString())); } } @override Future<void> close() { _connectivitySubscription?.cancel(); return super.close(); } } // Hive离线任务实体 @HiveType(typeId: 0) class OfflineTask extends HiveObject { @HiveField(0) final String id; @HiveField(1) final String content; @HiveField(2) final int retryCount; @HiveField(3) final DateTime createdAt; OfflineTask({ required this.id, required this.content, this.retryCount = 0, required this.createdAt, }); }
2. 实现重试机制与幂等性保障
- 幂等性:每个离线任务生成唯一ID,服务端根据ID判断是否已处理,避免重复提交导致数据冗余。
- 指数退避重试:同步失败时(如网络波动),按照1s、2s、4s的指数间隔重试,同时记录重试次数,超过阈值则标记为失败,等待用户手动触发重试。
3. 同步状态的UI反馈
在UI层监听OfflineSyncCubit的状态,给用户明确的同步状态提示:
BlocBuilder<OfflineSyncCubit, SyncState>( builder: (context, state) { if (state is SyncLoading) { return Padding( padding: EdgeInsets.all(8), child: CircularProgressIndicator(size: 20), ); } else if (state is SyncFailure) { return TextButton( onPressed: () => context.read<OfflineSyncCubit>()._syncLocalTasks(), child: Text('重试同步'), ); } return SizedBox.shrink(); }, )
4. 任务提交时的即时处理
用户提交数据时,先判断网络状态:无网则直接存入Hive;有网则先尝试发送,失败后仍存入Hive兜底:
class MessageBloc extends Bloc<MessageEvent, MessageState> { final OfflineSyncCubit offlineSyncCubit; final ApiService apiService; final Connectivity connectivity; MessageBloc({ required this.offlineSyncCubit, required this.apiService, required this.connectivity, }) : super(MessageInitial()) {} void sendMessage(String content) async { final task = OfflineTask( id: Uuid().v4(), content: content, createdAt: DateTime.now(), ); final networkStatus = await connectivity.checkConnectivity(); if (networkStatus == ConnectivityResult.none) { final box = await Hive.openBox('offline_tasks'); await box.put(task.id, task); } else { try { await apiService.submitTask(task); } catch (e) { // 发送失败存入本地,等待后续同步 final box = await Hive.openBox('offline_tasks'); await box.put(task.id, task); } } } }
5. 避免阻塞UI线程
所有Hive操作和网络请求都用异步方法执行,若批量同步任务较多,可考虑用Isolate处理,防止UI卡顿。
内容的提问来源于stack exchange,提问作者Gwaine
相关产品推荐
相关产品推荐

