Preview:
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);
    }
  }
}
downloadDownload PNG downloadDownload JPEG downloadDownload SVG

Tip: You can change the style, width & colours of the snippet with the inspect tool before clicking Download!

Click to optimize width for Twitter