Firebase Api Client With Cache
Mon Mar 31 2025 10:48:24 GMT+0000 (Coordinated Universal Time)
Saved by @Samuel1347 #dart #flutter #localnotification #firebase #firestore
import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import '../entities/api_errors.dart'; import '../services/storage_service.dart'; enum FirebaseApiFilterType { isEqualTo, isNotEqualTo, isGreaterThan, isGreaterThanOrEqualTo, isLessThan, isLessThanOrEqualTo, arrayContains, arrayContainsAny, whereIn, whereNotIn, isNull, } class FirebaseFilterEntity { final String field; final FirebaseApiFilterType operator; final dynamic value; FirebaseFilterEntity({ required this.field, required this.operator, required this.value, }); } abstract class FirebaseApiClient { Future<dynamic> get(String collection,{int limit = 20 ,List<FirebaseFilterEntity>? filters}); Future<dynamic> getById(String collection, String id); Future<dynamic> postWithId(String collection, {required Map<String, dynamic> params}); Future<void> post(String collection, {required String id, required Map<String, dynamic> params}); Future<void> putWithId(String collection, {required String id, required Map<String, dynamic> params}); Future<void> put(String collection, {required Map<String, dynamic> params}); Future<void> deleteDoc(String collection, String id); } class FirebaseApiClientImpl extends FirebaseApiClient { final FirebaseFirestore _client = FirebaseFirestore.instance; // ================ CACHE ================ // static const Duration firebaseCacheDuration = Duration(minutes: 10); // или сколько нужно String _buildCacheKey(String collection, [String? id]) => id != null ? 'firebase_cache_$collection\_$id' : 'firebase_cache_$collection'; String _buildTimestampKey(String collection, [String? id]) => id != null ? 'firebase_cache_time_$collection\_$id' : 'firebase_cache_time_$collection'; final _storageService = StorageService(); bool _isCacheValid(DateTime? cachedTime) { if (cachedTime == null) return false; final now = DateTime.now(); return now.difference(cachedTime) < firebaseCacheDuration; } // ================ METHODS ================ // @override Future<dynamic> get(String collection,{int limit = 20 ,List<FirebaseFilterEntity>? filters}) async { try { final cacheKey = _buildCacheKey(collection); final timeKey = _buildTimestampKey(collection); // Получение из кэша final cachedTimeRaw = await _storageService.get(key: timeKey); final cachedData = await _storageService.get(key: cacheKey); final cachedTime = cachedTimeRaw is String ? DateTime.tryParse(cachedTimeRaw) : null; if (_isCacheValid(cachedTime) && cachedData != null) { debugPrint("⚡️ Firebase cache hit: $collection"); return cachedData; } debugPrint("🔥 Firebase fetch: $collection"); Query query = _client.collection(collection); if (filters != null) { for (var filter in filters) { switch (filter.operator) { case FirebaseApiFilterType.isEqualTo: query = query.where(filter.field, isEqualTo: filter.value); break; case FirebaseApiFilterType.isNotEqualTo: query = query.where(filter.field, isNotEqualTo: filter.value); break; case FirebaseApiFilterType.isGreaterThan: query = query.where(filter.field, isGreaterThan: filter.value); break; case FirebaseApiFilterType.isGreaterThanOrEqualTo: query = query.where(filter.field, isGreaterThanOrEqualTo: filter.value); break; case FirebaseApiFilterType.isLessThan: query = query.where(filter.field, isLessThan: filter.value); break; case FirebaseApiFilterType.isLessThanOrEqualTo: query = query.where(filter.field, isLessThanOrEqualTo: filter.value); break; case FirebaseApiFilterType.arrayContains: query = query.where(filter.field, arrayContains: filter.value); break; case FirebaseApiFilterType.arrayContainsAny: query = query.where(filter.field, arrayContainsAny: filter.value); break; case FirebaseApiFilterType.whereIn: query = query.where(filter.field, whereIn: filter.value); break; case FirebaseApiFilterType.whereNotIn: query = query.where(filter.field, whereNotIn: filter.value); break; case FirebaseApiFilterType.isNull: query = query.where(filter.field, isNull: filter.value); break; default: throw ArgumentError('Unsupported operator: ${filter.operator}'); } } } final response = await query.limit(limit).get(); final result = response.docs.map((doc) => doc.data()).toList(); // Сохраняем в кэш await _storageService.save(key: cacheKey, value: result); await _storageService.save(key: timeKey, value: DateTime.now().toIso8601String()); return result; } catch (e) { throw _handleError(e, "errorGettingDataCollection".tr); } } @override Future<dynamic> getById(String collection, String id) async { try { final response = await _client.collection(collection).doc(id).get(); if (!response.exists) { throw Exception('Document with ID $id not found in collection $collection'); } return response.data(); } catch (e) { throw _handleError(e, "errorGettingDocumentById".tr); } } @override Future<void> put(String collection, {required Map<String, dynamic> params}) async { if (!params.containsKey('id')) { throw ArgumentError('documentIdIsRequiredToUpdate'.tr); } try { await _client.collection(collection).doc(params['id']).update(params); } catch (e) { throw _handleError(e, 'errorUpdatingDocument'.tr); } } @override Future<void> deleteDoc(String collection, String id) async { try { await _client.collection(collection).doc(id).delete(); } catch (e) { throw _handleError(e, 'errorDeletingDocument'.tr); } } Exception _handleError(dynamic error, String defaultMessage) { if (error is FirebaseException) { switch (error.code) { case 'permission-denied': return UnauthorisedException(); case 'not-found': return Exception(defaultMessage); default: return ExceptionWithMessage('$defaultMessage: ${error.message}'); } } return Exception(defaultMessage); } @override Future<void> post(String collection, {required String id, required Map<String, dynamic> params}) async { try { await _client.collection(collection).doc(id).set(params); } catch (e) { throw _handleError(e, 'errorPostingDataCollection'.tr); } } @override Future<dynamic> postWithId(String collection, {required Map<String, dynamic> params}) async { try { debugPrint("Post data $collection\n$params"); await _client.collection(collection).doc(params['id']).set(params); return params; } catch (e) { throw _handleError(e, 'errorPostingDataCollection'.tr); } } @override Future<void> putWithId(String collection, {required String id, required Map<String, dynamic> params}) async { try { await _client.collection(collection).doc(id).update(params); } catch (e) { throw _handleError(e, 'errorUpdatingDocument'.tr); } } }
Comments