remote_caching 1.0.12
remote_caching: ^1.0.12 copied to clipboard
A Flutter package for caching remote API calls with configurable duration.
๐ Remote Caching ๐ฆ
A lightweight, flexible, and persistent cache layer for remote API calls in Flutter.
Avoid redundant network calls. Boost performance. Cache smartly.
A lightweight yet powerful Flutter package for caching asynchronous remote calls locally using SQLite โ with full support for expiration, serialization, and custom deserializers.
๐ง Save your API responses. ๐ Avoid unnecessary calls. โก Go fast. ๐ก Stay clean.
๐ Table of Contents #
- โจ Features
- ๐ฏ Why RemoteCaching?
- ๐ Quick Start
- ๐ ๏ธ Usage Guide
- ๐ API Reference
- ๐ก Advanced Usage
- ๐ฆ Complete Example
- โ FAQ
- ๐ค Contributing
โจ Features #
- โ Automatic caching of remote data with intelligent expiration
- โณ Flexible expiration - use duration or exact datetime
- ๐ Manual cache invalidation (by key or clear all)
- ๐พ SQLite-powered persistent cache with automatic cleanup
- ๐งฉ Generic support for any data type (
Map
,List
, custom models...) - ๐งฐ 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
- ๐ง Cross-platform support (iOS, Android, Web, Desktop)
๐ฏ Why RemoteCaching? #
- ๐ You need structured, persistent caching for remote API calls
- ๐ก You want fine-grained control over serialization and expiration
- ๐งผ You don't want to reinvent the wheel each time you need cache logic
- โก You want to reduce API calls and improve app performance
- ๐ก๏ธ You need reliable error handling that won't break your app
๐ Quick Start #
1. Add the dependency #
flutter pub add remote_caching
2. Initialize the cache #
import 'package:remote_caching/remote_caching.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await RemoteCaching.instance.init(
defaultCacheDuration: Duration(hours: 1),
verboseMode: true, // See logs in debug mode
);
runApp(MyApp());
}
3. Cache your first API call #
class UserService {
Future<User> getUserProfile(String userId) async {
return await RemoteCaching.instance.call<User>(
'user_$userId',
cacheDuration: Duration(minutes: 30),
remote: () async => await fetchUserFromAPI(userId),
fromJson: (json) => User.fromJson(json as Map<String, dynamic>),
);
}
}
๐ ๏ธ Usage Guide #
๐ Initialization #
Initialize the cache system with your preferred settings:
await RemoteCaching.instance.init(
defaultCacheDuration: Duration(hours: 1), // Default cache duration
verboseMode: true, // Enable detailed logging (default: kDebugMode)
databasePath: '/custom/path/cache.db', // Custom database path (optional)
);
Parameters:
defaultCacheDuration
: Default expiration time for cached itemsverboseMode
: Enable detailed logging for debuggingdatabasePath
: Custom database path (uses default if not specified)
๐ Basic Caching #
Cache a simple API call with automatic expiration:
final user = await RemoteCaching.instance.call<User>(
'user_profile_123',
cacheDuration: Duration(minutes: 30),
remote: () async => await apiService.getUser(123),
fromJson: (json) => User.fromJson(json as Map<String, dynamic>),
);
โฐ Exact Expiration Time #
Use a specific expiration datetime instead of duration:
final user = await RemoteCaching.instance.call<User>(
'user_profile_123',
cacheExpiring: DateTime.now().add(Duration(hours: 2)),
remote: () async => await apiService.getUser(123),
fromJson: (json) => User.fromJson(json as Map<String, dynamic>),
);
๐ Caching Lists and Complex Data #
Cache lists, maps, or any serializable data:
// Cache a list of users
final users = await RemoteCaching.instance.call<List<User>>(
'all_users',
remote: () async => await apiService.getAllUsers(),
fromJson: (json) => (json as List)
.map((item) => User.fromJson(item as Map<String, dynamic>))
.toList(),
);
// Cache a map of settings
final settings = await RemoteCaching.instance.call<Map<String, dynamic>>(
'app_settings',
remote: () async => await apiService.getSettings(),
fromJson: (json) => Map<String, dynamic>.from(json as Map),
);
๐ Force Refresh #
Bypass cache and fetch fresh data:
final user = await RemoteCaching.instance.call<User>(
'user_profile_123',
forceRefresh: true, // Ignore cache, fetch from remote
remote: () async => await apiService.getUser(123),
fromJson: (json) => User.fromJson(json as Map<String, dynamic>),
);
๐งน Cache Management #
Clear cache entries as needed:
// Clear specific cache entry
await RemoteCaching.instance.clearCacheForKey('user_profile_123');
// Clear all cache
await RemoteCaching.instance.clearCache();
๐ Cache Statistics #
Monitor your cache usage:
final stats = await RemoteCaching.instance.getCacheStats();
print('Total entries: ${stats.totalEntries}');
print('Total size: ${stats.totalSizeBytes} bytes');
print('Expired entries: ${stats.expiredEntries}');
๐ก Advanced Usage #
๐ง In-Memory Database #
Use in-memory database for testing or temporary caching:
import 'package:remote_caching/src/common/get_in_memory_database.dart';
await RemoteCaching.instance.init(
databasePath: getInMemoryDatabasePath(),
verboseMode: true,
);
โ ๏ธ Warning: In-memory cache is lost on app restart. Avoid storing large datasets.
๐ Dynamic Cache Keys #
Generate cache keys dynamically based on parameters:
class ProductService {
Future<Product> getProduct(String category, String id) async {
final cacheKey = 'product_${category}_$id';
return await RemoteCaching.instance.call<Product>(
cacheKey,
remote: () async => await apiService.getProduct(category, id),
fromJson: (json) => Product.fromJson(json as Map<String, dynamic>),
);
}
}
๐ก๏ธ Error Handling #
The package handles serialization errors gracefully:
// If serialization fails, the remote call is used instead
// No app crashes, just logged errors
final data = await RemoteCaching.instance.call<ComplexModel>(
'complex_data',
remote: () async => await fetchComplexData(),
fromJson: (json) => ComplexModel.fromJson(json as Map<String, dynamic>),
);
๐ Cache Invalidation Strategies #
Implement different cache invalidation patterns:
class CacheManager {
// Invalidate related cache entries
Future<void> invalidateUserCache(String userId) async {
await RemoteCaching.instance.clearCacheForKey('user_$userId');
await RemoteCaching.instance.clearCacheForKey('user_profile_$userId');
await RemoteCaching.instance.clearCacheForKey('user_settings_$userId');
}
// Invalidate all cache when user logs out
Future<void> onUserLogout() async {
await RemoteCaching.instance.clearCache();
}
}
๐ฆ Complete Example #
Here's a complete example showing how to cache API responses in a Flutter app:
import 'package:flutter/material.dart';
import 'package:remote_caching/remote_caching.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
class UserProfilePage extends StatefulWidget {
@override
_UserProfilePageState createState() => _UserProfilePageState();
}
class _UserProfilePageState extends State<UserProfilePage> {
User? _user;
bool _isLoading = false;
String? _error;
@override
void initState() {
super.initState();
_loadUserProfile();
}
Future<void> _loadUserProfile() async {
setState(() {
_isLoading = true;
_error = null;
});
try {
final user = await RemoteCaching.instance.call<User>(
'user_profile_123',
cacheDuration: Duration(minutes: 30),
remote: () async {
final response = await http.get(
Uri.parse('https://api.example.com/users/123'),
);
if (response.statusCode == 200) {
return jsonDecode(response.body);
} else {
throw Exception('Failed to load user profile');
}
},
fromJson: (json) => User.fromJson(json as Map<String, dynamic>),
);
setState(() {
_user = user;
_isLoading = false;
});
} catch (e) {
setState(() {
_error = e.toString();
_isLoading = false;
});
}
}
Future<void> _refreshProfile() async {
// Force refresh from remote
final user = await RemoteCaching.instance.call<User>(
'user_profile_123',
forceRefresh: true,
remote: () async {
final response = await http.get(
Uri.parse('https://api.example.com/users/123'),
);
return jsonDecode(response.body);
},
fromJson: (json) => User.fromJson(json as Map<String, dynamic>),
);
setState(() {
_user = user;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('User Profile'),
actions: [
IconButton(
icon: Icon(Icons.refresh),
onPressed: _refreshProfile,
),
],
),
body: _buildBody(),
);
}
Widget _buildBody() {
if (_isLoading) {
return Center(child: CircularProgressIndicator());
}
if (_error != null) {
return Center(child: Text('Error: $_error'));
}
if (_user == null) {
return Center(child: Text('No user data'));
}
return Padding(
padding: EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Name: ${_user!.name}', style: Theme.of(context).textTheme.headline6),
Text('Email: ${_user!.email}'),
Text('Age: ${_user!.age}'),
],
),
);
}
}
class User {
final String name;
final String email;
final int age;
User({required this.name, required this.email, required this.age});
factory User.fromJson(Map<String, dynamic> json) {
return User(
name: json['name'] as String,
email: json['email'] as String,
age: json['age'] as int,
);
}
}
A full working example is available in the example/
directory.
๐ API Reference #
RemoteCaching Class #
The main class for managing remote caching operations.
Methods
Method | Description | Parameters |
---|---|---|
init() |
Initialize the cache system | defaultCacheDuration , verboseMode , databasePath |
call<T>() |
Cache a remote call | key , remote , fromJson , cacheDuration , cacheExpiring , forceRefresh |
clearCache() |
Clear all cache entries | None |
clearCacheForKey() |
Clear specific cache entry | key |
getCacheStats() |
Get cache statistics | None |
dispose() |
Clean up resources | None |
Parameters Details
init()
parameters:
defaultCacheDuration
(Duration?): Default expiration time for cached itemsverboseMode
(bool): Enable detailed logging (default:kDebugMode
)databasePath
(String?): Custom database path
call<T>()
parameters:
key
(String): Unique identifier for the cache entryremote
(FuturefromJson
(T Function(Object? json)): Function to deserialize JSON datacacheDuration
(Duration?): How long to cache the datacacheExpiring
(DateTime?): Exact expiration datetimeforceRefresh
(bool): Bypass cache and fetch fresh data
CachingStats Class #
Statistics about the current cache state.
class CachingStats {
final int totalEntries; // Total number of cached entries
final int totalSizeBytes; // Total size of cached data in bytes
final int expiredEntries; // Number of expired entries
}
โ 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: 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.
Q: Does it work offline?
A: Cached data is available offline until it expires or is cleared.
Q: Does it work on all platforms?
A: We use sqlite3 with sqflite_common_ffi to support all platforms. Refer to the packages docs for more information.
Q: Can I use a custom database path?
A: Yes! You can specify a custom database path using the databasePath
parameter in the init()
method.
Q: How do I handle cache invalidation?
A: Use clearCacheForKey()
for specific entries or clearCache()
for all entries. You can also use forceRefresh: true
to bypass cache for a single call.
Q: What's the difference between cacheDuration
and cacheExpiring
?
A: cacheDuration
sets expiration relative to now (e.g., 30 minutes from now), while cacheExpiring
sets an absolute expiration datetime.
Q: Can I cache different types of data?
A: Yes! You can cache any serializable data: primitives, maps, lists, custom objects, etc. Just provide the appropriate fromJson
function.
Q: Is the cache persistent?
A: Yes, by default the cache is stored in SQLite and persists between app launches. Use getInMemoryDatabasePath()
for temporary in-memory caching.
Q: How do I monitor cache performance?
A: Use getCacheStats()
to get statistics about cache usage, or enable verboseMode
to see detailed logs.
๐ค Contributing #
Contributions, issues and feature requests are welcome! Feel free to check issues page or submit a pull request.
Code Style #
This project follows the very_good_analysis linting rules.
Made with โค๏ธ by Eliatolin