get<T> method

Future<T?> get<T>(
  1. String key, {
  2. Future<T> refreshCallback()?,
  3. CachePolicy? policy,
})

Retrieves the value associated with the given key.

Returns null if no value is found for the given key or if the value is expired.

If refreshCallback is provided and the item is stale according to its policy, the callback will be used to refresh the data based on the refresh strategy.

Throws an ArgumentError if the key is empty. Throws a CacheException if there is an error retrieving the data.

Implementation

Future<T?> get<T>(String key,
    {Future<T> Function()? refreshCallback, CachePolicy? policy}) async {
  if (key.isEmpty) {
    throw ArgumentError('Key cannot be empty');
  }
  try {
    final cacheItem = await _cacheAdapter.get(key);
    if (cacheItem == null) {
      // Record cache miss in analytics
      _analytics.recordMiss(key);

      // If a refresh callback is provided, use it to get fresh data
      if (refreshCallback != null) {
        final freshValue = await refreshCallback();
        await put(key, freshValue, policy: policy);
        return freshValue;
      }

      return null;
    }

    if (cacheItem.isExpired) {
      // Record cache miss in analytics
      _analytics.recordMiss(key);
      await delete(key);

      // If a refresh callback is provided, use it to get fresh data
      if (refreshCallback != null) {
        final freshValue = await refreshCallback();
        await put(key, freshValue, policy: policy);
        return freshValue;
      }

      return null;
    }

    // Check if the item is stale and needs refreshing
    final effectivePolicy = policy ?? CachePolicy.defaultPolicy;
    if (refreshCallback != null &&
        effectivePolicy.staleTime != null &&
        cacheItem.isStale(effectivePolicy.staleTime!)) {
      // Handle different refresh strategies
      switch (effectivePolicy.refreshStrategy) {
        case RefreshStrategy.backgroundRefresh:
          // Update in the background without blocking
          _refreshInBackground(key, refreshCallback, effectivePolicy);
          break;
        case RefreshStrategy.immediateRefresh:
          // Refresh immediately and return the fresh value
          final freshValue = await refreshCallback();
          await put(key, freshValue, policy: effectivePolicy);
          return freshValue;
        case RefreshStrategy.never:
          // Do nothing, just use the cached value
          break;
      }
    }

    // Update sliding expiry if needed
    // Decompress the value if it's compressed
    T? resultValue;
    if (cacheItem.isCompressed &&
        _compression != null &&
        cacheItem.value is String) {
      try {
        final decompressedValue =
            _compression.decompressString(cacheItem.value as String);
        resultValue = decompressedValue as T?;
        _log.fine('Decompressed value for key $key');
      } catch (e) {
        _log.warning('Failed to decompress value for key $key: $e');
        resultValue = cacheItem.value as T?;
      }
    } else {
      resultValue = cacheItem.value as T?;
    }

    if (cacheItem.slidingExpiry != null) {
      final updatedCacheItem = cacheItem.updateExpiry();
      await _cacheAdapter.put(key, updatedCacheItem);
      // Record cache hit in analytics
      _analytics.recordHit(key);
      return resultValue;
    }

    // Update access metadata
    final updatedCacheItem = cacheItem.updateExpiry();
    if (updatedCacheItem != cacheItem) {
      await _cacheAdapter.put(key, updatedCacheItem);
    }

    // Record cache hit in analytics
    _analytics.recordHit(key);
    return resultValue;
  } on HiveError catch (e) {
    _log.severe('Failed to get data from cache (HiveError): $e');
    throw CacheException('Failed to get data from cache: ${e.message}');
  } catch (e) {
    _log.severe('Failed to get data from cache (Unknown Error): $e');
    throw CacheException('Failed to get data from cache: $e');
  }
}