Skip to content

Commit 9c1f52b

Browse files
authored
Merge pull request #11 from EliaTolin/feature/error-handler
feat: add onError callback for custom error handling
2 parents 5bd88bf + ba136d3 commit 9c1f52b

7 files changed

Lines changed: 477 additions & 13 deletions

File tree

CHANGELOG.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
## 1.0.17
2+
3+
* Added `onError` callback to `call()` method for custom error handling
4+
* New `CacheError` class with detailed error information (key, type, error, stackTrace, rawData)
5+
* New `CacheErrorType` enum for distinguishing serialization vs deserialization errors
6+
* Backward compatible: errors still fallback gracefully without callback
7+
18
## 1.0.16
29

310
* Added `CacheStrategy` enum with `cacheFirst` and `networkFirst` strategies
@@ -27,7 +34,7 @@
2734

2835
## 1.0.10
2936

30-
* Remove support for web
37+
* Remove support for web
3138

3239
## 1.0.9
3340

@@ -60,4 +67,4 @@
6067

6168
## 0.0.1
6269

63-
* Initial release
70+
* Initial release

README.md

Lines changed: 75 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ A lightweight yet powerful Flutter package for caching asynchronous remote calls
6161
- 🧰 **Custom deserialization** with `fromJson` functions
6262
- 📊 **Cache statistics** and monitoring
6363
- 🧪 **Test-friendly** with verbose logging and in-memory database support
64-
- 🛡️ **Error handling** - graceful fallback to remote calls
64+
- 🛡️ **Error handling** - graceful fallback to remote calls with optional error callbacks
6565
- 🔧 **Cross-platform** support (iOS, Android, Desktop)
6666

6767
## 🎯 Why RemoteCaching?
@@ -299,7 +299,7 @@ class ProductService {
299299

300300
### 🛡️ Error Handling
301301

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

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

314+
#### Custom Error Handling with `onError`
315+
316+
For more control over error handling, use the `onError` callback to capture and handle cache errors:
317+
318+
```dart
319+
final data = await RemoteCaching.instance.call<User>(
320+
'user_profile',
321+
remote: () async => await fetchUser(),
322+
fromJson: (json) => User.fromJson(json as Map<String, dynamic>),
323+
onError: (error) {
324+
// Log to external service (Sentry, Datadog, etc.)
325+
analytics.logError('cache_error', {
326+
'key': error.key,
327+
'type': error.type.name,
328+
'message': error.message,
329+
});
330+
331+
// Or handle specific error types
332+
switch (error.type) {
333+
case CacheErrorType.serialization:
334+
print('Failed to save data to cache');
335+
break;
336+
case CacheErrorType.deserializationJson:
337+
print('Cached data is corrupted');
338+
break;
339+
case CacheErrorType.deserializationFromJson:
340+
print('Schema mismatch in cached data');
341+
break;
342+
}
343+
},
344+
);
345+
```
346+
347+
The `CacheError` class provides:
348+
- `key`: The cache key that failed
349+
- `type`: The type of error (`serialization`, `deserializationJson`, `deserializationFromJson`)
350+
- `error`: The underlying exception
351+
- `stackTrace`: Stack trace for debugging
352+
- `rawData`: The data that failed to serialize/deserialize (if available)
353+
- `message`: Human-readable error message
354+
314355
### 🔄 Cache Invalidation Strategies
315356

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

524566
### CacheStrategy Enum
525567

@@ -532,6 +574,31 @@ enum CacheStrategy {
532574
}
533575
```
534576

577+
### CacheError Class
578+
579+
Error information for cache operations.
580+
581+
```dart
582+
class CacheError {
583+
final String key; // Cache key that failed
584+
final CacheErrorType type; // Type of error
585+
final Object error; // Underlying exception
586+
final StackTrace stackTrace; // Stack trace
587+
final Object? rawData; // Data that failed (if available)
588+
String get message; // Human-readable error message
589+
}
590+
```
591+
592+
### CacheErrorType Enum
593+
594+
```dart
595+
enum CacheErrorType {
596+
serialization, // jsonEncode failed
597+
deserializationJson, // jsonDecode failed
598+
deserializationFromJson, // fromJson function threw
599+
}
600+
```
601+
535602
### CachingStats Class
536603

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

549616
## ❓ FAQ
550617

551-
**Q: What happens if serialization or deserialization fails?**
552-
A: The error is logged, the cache is ignored, and the remote call is used. Your app will never crash due to cache errors.
618+
**Q: What happens if serialization or deserialization fails?**
619+
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.
620+
621+
**Q: How can I monitor cache errors in production?**
622+
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.
553623

554624
**Q: Can I use my own model classes?**
555625
A: Yes! Just provide a `fromJson` function and ensure your model supports `toJson` when caching. The package relies on `jsonEncode` / `jsonDecode` under the hood.

lib/remote_caching.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
/// - [RemoteCaching] - The main class for managing remote caching operations
2424
/// - [CachingStats] - Statistics about the current cache state
2525
/// - [CacheStrategy] - Enum for controlling cache vs network behavior
26+
/// - [CacheError] - Error information for cache operations
27+
/// - [CacheErrorType] - Type of cache error that occurred
2628
/// - [getInMemoryDatabasePath] - Utility for creating in-memory databases (testing)
2729
///
2830
/// For detailed documentation and examples, see the individual class documentation.
@@ -31,6 +33,7 @@ library remote_caching;
3133
import 'package:remote_caching/remote_caching.dart';
3234

3335
export 'src/common/get_in_memory_database.dart' show getInMemoryDatabasePath;
36+
export 'src/models/cache_error.dart' show CacheError, CacheErrorType;
3437
export 'src/models/cache_strategy.dart' show CacheStrategy;
3538
export 'src/models/caching_stats.dart' show CachingStats;
3639
export 'src/remote_caching_impl.dart' show RemoteCaching;

lib/src/models/cache_error.dart

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/// Represents an error that occurred during cache operations.
2+
///
3+
/// This class provides detailed information about serialization or
4+
/// deserialization errors that occur during caching, allowing developers
5+
/// to handle them appropriately.
6+
///
7+
/// ## Example
8+
/// ```dart
9+
/// final user = await RemoteCaching.instance.call<User>(
10+
/// 'user_profile',
11+
/// remote: () async => await fetchUser(),
12+
/// fromJson: (json) => User.fromJson(json as Map<String, dynamic>),
13+
/// onError: (error) {
14+
/// print('Cache error for ${error.key}: ${error.message}');
15+
/// // Log to analytics, retry, etc.
16+
/// },
17+
/// );
18+
/// ```
19+
class CacheError {
20+
/// Creates a new [CacheError] instance.
21+
const CacheError({
22+
required this.key,
23+
required this.type,
24+
required this.error,
25+
required this.stackTrace,
26+
this.rawData,
27+
});
28+
29+
/// The cache key associated with this error.
30+
final String key;
31+
32+
/// The type of cache error that occurred.
33+
final CacheErrorType type;
34+
35+
/// The underlying error/exception that was caught.
36+
final Object error;
37+
38+
/// The stack trace when the error occurred.
39+
final StackTrace stackTrace;
40+
41+
/// The raw data that failed to serialize/deserialize (if available).
42+
///
43+
/// For serialization errors, this is the original object.
44+
/// For deserialization errors, this is the JSON string from cache.
45+
final Object? rawData;
46+
47+
/// A human-readable message describing the error.
48+
String get message {
49+
switch (type) {
50+
case CacheErrorType.serialization:
51+
return 'Failed to serialize data for key "$key": $error';
52+
case CacheErrorType.deserializationJson:
53+
return 'Failed to decode JSON from cache for key "$key": $error';
54+
case CacheErrorType.deserializationFromJson:
55+
return 'Failed to convert JSON to object for key "$key": $error';
56+
}
57+
}
58+
59+
@override
60+
String toString() => 'CacheError($message)';
61+
}
62+
63+
/// The type of cache error that occurred.
64+
enum CacheErrorType {
65+
/// Error during JSON encoding (jsonEncode failed).
66+
///
67+
/// This occurs when the data returned from remote() cannot be
68+
/// converted to JSON for storage in the cache.
69+
serialization,
70+
71+
/// Error during JSON decoding (jsonDecode failed).
72+
///
73+
/// This occurs when the cached JSON string cannot be parsed.
74+
/// This might indicate corrupted cache data.
75+
deserializationJson,
76+
77+
/// Error during fromJson conversion.
78+
///
79+
/// This occurs when the JSON was decoded successfully but
80+
/// the fromJson function threw an error during conversion.
81+
deserializationFromJson,
82+
}

0 commit comments

Comments
 (0)