Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
## 1.0.17

* Added `onError` callback to `call()` method for custom error handling
* New `CacheError` class with detailed error information (key, type, error, stackTrace, rawData)
* New `CacheErrorType` enum for distinguishing serialization vs deserialization errors
* Backward compatible: errors still fallback gracefully without callback

## 1.0.16

* Added `CacheStrategy` enum with `cacheFirst` and `networkFirst` strategies
Expand Down Expand Up @@ -27,7 +34,7 @@

## 1.0.10

* Remove support for web
* Remove support for web

## 1.0.9

Expand Down Expand Up @@ -60,4 +67,4 @@

## 0.0.1

* Initial release
* Initial release
80 changes: 75 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ A lightweight yet powerful Flutter package for caching asynchronous remote calls
- 🧰 **Custom deserialization** with `fromJson` functions
- 📊 **Cache statistics** and monitoring
- 🧪 **Test-friendly** with verbose logging and in-memory database support
- 🛡️ **Error handling** - graceful fallback to remote calls
- 🛡️ **Error handling** - graceful fallback to remote calls with optional error callbacks
- 🔧 **Cross-platform** support (iOS, Android, Desktop)

## 🎯 Why RemoteCaching?
Expand Down Expand Up @@ -299,7 +299,7 @@ class ProductService {

### 🛡️ Error Handling

The package handles serialization errors gracefully:
The package handles serialization errors gracefully. By default, errors are logged and the remote call is used as fallback:

```dart
// If serialization fails, the remote call is used instead
Expand All @@ -311,6 +311,47 @@ final data = await RemoteCaching.instance.call<ComplexModel>(
);
```

#### Custom Error Handling with `onError`

For more control over error handling, use the `onError` callback to capture and handle cache errors:

```dart
final data = await RemoteCaching.instance.call<User>(
'user_profile',
remote: () async => await fetchUser(),
fromJson: (json) => User.fromJson(json as Map<String, dynamic>),
onError: (error) {
// Log to external service (Sentry, Datadog, etc.)
analytics.logError('cache_error', {
'key': error.key,
'type': error.type.name,
'message': error.message,
});

// Or handle specific error types
switch (error.type) {
case CacheErrorType.serialization:
print('Failed to save data to cache');
break;
case CacheErrorType.deserializationJson:
print('Cached data is corrupted');
break;
case CacheErrorType.deserializationFromJson:
print('Schema mismatch in cached data');
break;
}
},
);
```

The `CacheError` class provides:
- `key`: The cache key that failed
- `type`: The type of error (`serialization`, `deserializationJson`, `deserializationFromJson`)
- `error`: The underlying exception
- `stackTrace`: Stack trace for debugging
- `rawData`: The data that failed to serialize/deserialize (if available)
- `message`: Human-readable error message

### 🔄 Cache Invalidation Strategies

Implement different cache invalidation patterns:
Expand Down Expand Up @@ -498,7 +539,7 @@ The main class for managing remote caching operations.
| Method | Description | Parameters |
|--------|-------------|------------|
| `init()` | Initialize the cache system | `defaultCacheDuration`, `verboseMode`, `databasePath` |
| `call<T>()` | Cache a remote call | `key`, `remote`, `fromJson`, `cacheDuration`, `cacheExpiring`, `forceRefresh`, `strategy` |
| `call<T>()` | Cache a remote call | `key`, `remote`, `fromJson`, `cacheDuration`, `cacheExpiring`, `forceRefresh`, `strategy`, `onError` |
| `clearCache()` | Clear all cache entries | None |
| `clearCacheForKey()` | Clear specific cache entry | `key` |
| `clearCacheByPrefix()` | Clear all entries matching a prefix | `prefix` |
Expand All @@ -520,6 +561,7 @@ The main class for managing remote caching operations.
- `cacheExpiring` (DateTime?): Exact expiration datetime
- `forceRefresh` (bool): Bypass cache and fetch fresh data
- `strategy` (CacheStrategy): Cache strategy to use (default: `CacheStrategy.cacheFirst`)
- `onError` (void Function(CacheError)?): Callback for cache errors

### CacheStrategy Enum

Expand All @@ -532,6 +574,31 @@ enum CacheStrategy {
}
```

### CacheError Class

Error information for cache operations.

```dart
class CacheError {
final String key; // Cache key that failed
final CacheErrorType type; // Type of error
final Object error; // Underlying exception
final StackTrace stackTrace; // Stack trace
final Object? rawData; // Data that failed (if available)
String get message; // Human-readable error message
}
```

### CacheErrorType Enum

```dart
enum CacheErrorType {
serialization, // jsonEncode failed
deserializationJson, // jsonDecode failed
deserializationFromJson, // fromJson function threw
}
```

### CachingStats Class

Statistics about the current cache state.
Expand All @@ -548,8 +615,11 @@ class CachingStats {

## ❓ FAQ

**Q: What happens if serialization or deserialization fails?**
A: The error is logged, the cache is ignored, and the remote call is used. Your app will never crash due to cache errors.
**Q: What happens if serialization or deserialization fails?**
A: By default, the error is logged (in verbose mode), the cache is ignored, and the remote call is used. Your app will never crash due to cache errors. You can use the `onError` callback to capture and handle these errors for logging, metrics, or debugging.

**Q: How can I monitor cache errors in production?**
A: Use the `onError` callback to send errors to your analytics or monitoring service (Sentry, Datadog, etc.). The callback receives a `CacheError` object with details about the failure.

**Q: Can I use my own model classes?**
A: Yes! Just provide a `fromJson` function and ensure your model supports `toJson` when caching. The package relies on `jsonEncode` / `jsonDecode` under the hood.
Expand Down
3 changes: 3 additions & 0 deletions lib/remote_caching.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
/// - [RemoteCaching] - The main class for managing remote caching operations
/// - [CachingStats] - Statistics about the current cache state
/// - [CacheStrategy] - Enum for controlling cache vs network behavior
/// - [CacheError] - Error information for cache operations
/// - [CacheErrorType] - Type of cache error that occurred
/// - [getInMemoryDatabasePath] - Utility for creating in-memory databases (testing)
///
/// For detailed documentation and examples, see the individual class documentation.
Expand All @@ -31,6 +33,7 @@ library remote_caching;
import 'package:remote_caching/remote_caching.dart';

export 'src/common/get_in_memory_database.dart' show getInMemoryDatabasePath;
export 'src/models/cache_error.dart' show CacheError, CacheErrorType;
export 'src/models/cache_strategy.dart' show CacheStrategy;
export 'src/models/caching_stats.dart' show CachingStats;
export 'src/remote_caching_impl.dart' show RemoteCaching;
82 changes: 82 additions & 0 deletions lib/src/models/cache_error.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/// Represents an error that occurred during cache operations.
///
/// This class provides detailed information about serialization or
/// deserialization errors that occur during caching, allowing developers
/// to handle them appropriately.
///
/// ## Example
/// ```dart
/// final user = await RemoteCaching.instance.call<User>(
/// 'user_profile',
/// remote: () async => await fetchUser(),
/// fromJson: (json) => User.fromJson(json as Map<String, dynamic>),
/// onError: (error) {
/// print('Cache error for ${error.key}: ${error.message}');
/// // Log to analytics, retry, etc.
/// },
/// );
/// ```
class CacheError {
/// Creates a new [CacheError] instance.
const CacheError({
required this.key,
required this.type,
required this.error,
required this.stackTrace,
this.rawData,
});

/// The cache key associated with this error.
final String key;

/// The type of cache error that occurred.
final CacheErrorType type;

/// The underlying error/exception that was caught.
final Object error;

/// The stack trace when the error occurred.
final StackTrace stackTrace;

/// The raw data that failed to serialize/deserialize (if available).
///
/// For serialization errors, this is the original object.
/// For deserialization errors, this is the JSON string from cache.
final Object? rawData;

/// A human-readable message describing the error.
String get message {
switch (type) {
case CacheErrorType.serialization:
return 'Failed to serialize data for key "$key": $error';
case CacheErrorType.deserializationJson:
return 'Failed to decode JSON from cache for key "$key": $error';
case CacheErrorType.deserializationFromJson:
return 'Failed to convert JSON to object for key "$key": $error';
}
}

@override
String toString() => 'CacheError($message)';
}

/// The type of cache error that occurred.
enum CacheErrorType {
/// Error during JSON encoding (jsonEncode failed).
///
/// This occurs when the data returned from remote() cannot be
/// converted to JSON for storage in the cache.
serialization,

/// Error during JSON decoding (jsonDecode failed).
///
/// This occurs when the cached JSON string cannot be parsed.
/// This might indicate corrupted cache data.
deserializationJson,

/// Error during fromJson conversion.
///
/// This occurs when the JSON was decoded successfully but
/// the fromJson function threw an error during conversion.
deserializationFromJson,
}
Loading