remote_caching 1.0.15 copy "remote_caching: ^1.0.15" to clipboard
remote_caching: ^1.0.15 copied to clipboard

A Flutter package for caching remote API calls with configurable duration.

Remote Caching logo

πŸ” Remote Caching πŸ“¦

A lightweight, flexible, and persistent cache layer for remote API calls in Flutter.
Avoid redundant network calls. Boost performance. Cache smartly.

Pub Points Pub Version GitHub Stars Very Good Analysis


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 #

  • βœ… Automatic caching of remote data with intelligent expiration
  • ⏳ Flexible expiration - use duration or exact datetime
  • πŸ”„ Manual cache invalidation (by key, prefix, 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, 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 items
  • verboseMode: Enable detailed logging for debugging
  • databasePath: 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 entries with a specific prefix
final deleted = await RemoteCaching.instance.clearCacheByPrefix('user_');
print('Deleted $deleted entries');

// 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 all cache entries for a specific user using prefix
  Future<void> invalidateUserCache(String userId) async {
    final deleted = await RemoteCaching.instance.clearCacheByPrefix('user_${userId}_');
    print('Invalidated $deleted cache entries for user $userId');
  }

  // Invalidate all user-related cache entries
  Future<void> invalidateAllUsersCache() async {
    await RemoteCaching.instance.clearCacheByPrefix('user_');
  }

  // Invalidate a specific cache entry
  Future<void> invalidateSpecificCache(String key) async {
    await RemoteCaching.instance.clearCacheForKey(key);
  }

  // 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
clearCacheByPrefix() Clear all entries matching a prefix prefix
getCacheStats() Get cache statistics None
dispose() Clean up resources None

Parameters Details

init() parameters:

  • defaultCacheDuration (Duration?): Default expiration time for cached items
  • verboseMode (bool): Enable detailed logging (default: kDebugMode)
  • databasePath (String?): Custom database path

call<T>() parameters:

  • key (String): Unique identifier for the cache entry
  • remote (Future
  • fromJson (T Function(Object? json)): Function to deserialize JSON data
  • cacheDuration (Duration?): How long to cache the data
  • cacheExpiring (DateTime?): Exact expiration datetime
  • forceRefresh (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, clearCacheByPrefix() for groups of related entries (e.g., all user data), 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

32
likes
160
points
118
downloads
screenshot

Publisher

verified publishereliatolin.it

Weekly Downloads

A Flutter package for caching remote API calls with configurable duration.

Repository (GitHub)
View/report issues

Topics

#network #http #caching #remote

Documentation

API reference

License

BSD-3-Clause (license)

Dependencies

flutter, path, sqflite, sqflite_common_ffi, sqlite3_flutter_libs

More

Packages that depend on remote_caching