home / skills / kaakati / rails-enterprise-dev / get-storage-patterns

get-storage-patterns skill

/plugins/reactree-flutter-dev/skills/get-storage-patterns

This skill helps you implement robust local storage and caching with GetStorage, enabling offline-first apps and seamless state persistence.

npx playbooks add skill kaakati/rails-enterprise-dev --skill get-storage-patterns

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
4.3 KB
---
name: "GetStorage Patterns"
description: "Local storage with GetStorage for preferences, caching, and offline-first patterns"
version: "1.0.0"
---

# GetStorage Patterns

## Initialization

```dart
// main.dart
void main() async {
  await GetStorage.init();
  runApp(MyApp());
}
```

## Storage Service Pattern

```dart
class StorageService {
  final GetStorage _box;
  
  StorageService() : _box = GetStorage();
  
  // Token management
  String? get token => _box.read<String>('auth_token');
  Future<void> setToken(String token) => _box.write('auth_token', token);
  Future<void> clearToken() => _box.remove('auth_token');
  
  // User data
  Map<String, dynamic>? get userData => _box.read<Map<String, dynamic>>('user_data');
  Future<void> setUserData(Map<String, dynamic> data) => _box.write('user_data', data);
  
  // Preferences
  bool get isDarkMode => _box.read<bool>('dark_mode') ?? false;
  Future<void> setDarkMode(bool value) => _box.write('dark_mode', value);
  
  String get locale => _box.read<String>('locale') ?? 'en';
  Future<void> setLocale(String locale) => _box.write('locale', locale);
  
  // Clear all
  Future<void> clearAll() => _box.erase();
  
  // Listen to changes
  void listenKey(String key, Function(dynamic) callback) {
    _box.listenKey(key, callback);
  }
}
```

## Local Data Source Pattern

```dart
class UserLocalDataSource {
  final GetStorage _storage;
  static const String _usersKey = 'cached_users';
  static const String _userKeyPrefix = 'cached_user_';
  static const Duration _cacheDuration = Duration(hours: 24);
  
  UserLocalDataSource(this._storage);
  
  Future<void> cacheUser(UserModel user) async {
    final cacheData = {
      'user': user.toJson(),
      'timestamp': DateTime.now().toIso8601String(),
    };
    await _storage.write('$_userKeyPrefix${user.id}', cacheData);
  }
  
  Future<UserModel?> getCachedUser(String id) async {
    final cacheData = _storage.read<Map<String, dynamic>>('$_userKeyPrefix$id');
    
    if (cacheData == null) return null;
    
    // Check cache expiration
    final timestamp = DateTime.parse(cacheData['timestamp']);
    if (DateTime.now().difference(timestamp) > _cacheDuration) {
      await _storage.remove('$_userKeyPrefix$id');
      return null;
    }
    
    return UserModel.fromJson(cacheData['user']);
  }
  
  Future<void> cacheUsers(List<UserModel> users) async {
    final cacheData = {
      'users': users.map((u) => u.toJson()).toList(),
      'timestamp': DateTime.now().toIso8601String(),
    };
    await _storage.write(_usersKey, cacheData);
  }
  
  Future<List<UserModel>?> getCachedUsers() async {
    final cacheData = _storage.read<Map<String, dynamic>>(_usersKey);
    
    if (cacheData == null) return null;
    
    final timestamp = DateTime.parse(cacheData['timestamp']);
    if (DateTime.now().difference(timestamp) > _cacheDuration) {
      await _storage.remove(_usersKey);
      return null;
    }
    
    final List<dynamic> usersList = cacheData['users'];
    return usersList.map((json) => UserModel.fromJson(json)).toList();
  }
  
  Future<void> clearCache() async {
    await _storage.erase();
  }
}
```

## GetX Service Integration

```dart
class CacheService extends GetxService {
  final GetStorage _storage;
  
  CacheService() : _storage = GetStorage();
  
  Future<CacheService> init() async {
    await GetStorage.init();
    return this;
  }
  
  // Reactive cache
  final _cachedData = <String, dynamic>{}.obs;
  
  T? get<T>(String key) {
    return _storage.read<T>(key);
  }
  
  Future<void> put<T>(String key, T value, {Duration? expiry}) async {
    if (expiry != null) {
      final expiryData = {
        'value': value,
        'expiry': DateTime.now().add(expiry).toIso8601String(),
      };
      await _storage.write(key, expiryData);
    } else {
      await _storage.write(key, value);
    }
    _cachedData[key] = value;
  }
  
  Future<void> remove(String key) async {
    await _storage.remove(key);
    _cachedData.remove(key);
  }
  
  bool has(String key) {
    return _storage.hasData(key);
  }
}
```

## Best Practices

- Initialize GetStorage before running app
- Use type-safe reads (`read<String>`, `read<int>`, etc.)
- Implement cache expiration for time-sensitive data
- Clear cache on logout
- Use separate keys for different data types
- Listen to changes for reactive updates

Overview

This skill presents proven GetStorage patterns for local persistence in Flutter apps, focused on preferences, caching, and offline-first flows. I provide service and local data source patterns, reactive cache integration with GetX, and practical examples for tokens, user data, and expirations. The content helps teams adopt a consistent, type-safe approach to local storage and cache invalidation.

How this skill works

Initialize GetStorage at app startup, then use a dedicated StorageService for common keys (tokens, preferences, locale) to centralize reads/writes and listeners. For domain data, use a UserLocalDataSource that stores payloads alongside timestamps and enforces cache expiration before returning stale data. Optionally wrap storage in a GetxService CacheService to maintain a reactive in-memory mirror and support per-key expiry objects.

When to use it

  • Store authentication tokens and session info that must persist across launches.
  • Cache API responses (lists or single models) to enable offline-first behavior and faster UI loads.
  • Save user preferences (theme, locale) for immediate access and simple binding.
  • Implement short-lived caches for time-sensitive content (e.g., 24-hour user lists).
  • Listen to specific keys for reactive UI updates without rebuilding unrelated state.

Best practices

  • Call GetStorage.init() before runApp to ensure storage is available.
  • Use type-safe reads like read<String> or read<Map<String, dynamic>> to avoid runtime errors.
  • Store a timestamp with cached payloads and enforce expiration checks on read.
  • Keep separate keys per data type and domain to avoid collisions and simplify clears.
  • Clear sensitive data (tokens, user cache) on logout or account switch.
  • Use listenKey for targeted reactive updates instead of broad rebuilds.

Example use cases

  • Token management service: read, write, and erase auth_token with a single StorageService class.
  • Cached users list: write users + timestamp, return null when older than 24 hours to trigger refresh.
  • Per-user cache: save each user under cached_user_{id} and expire independently.
  • Reactive cache service: update an observable map when Put/Remove runs to notify UI controllers.
  • Preference toggles: persist dark_mode and locale values for immediate app-wide effect.

FAQ

Do I need to encrypt sensitive data stored with GetStorage?

Yes—GetStorage is not encrypted by default. For sensitive tokens or PII, layer platform encryption or use a secure storage plugin.

How do I handle migrations when changing stored shapes?

Version your keys or include a schema version in stored objects. On init, detect mismatches and transform or clear old entries.

Can I combine GetStorage with remote sync?

Yes—use cache timestamps to decide when to fetch fresh data, and write back successful remote responses to local storage for offline access.