Offline Setup
Offline Tier Configuration
Step-by-step guide for implementing each offline tier in your features.
Offline Setup
Section titled “Offline Setup”Step 1: Choose Your Tier
Section titled “Step 1: Choose Your Tier”Use the Offline Tiers Reference to determine the appropriate tier for your feature.
Quick decision guide:
- Data is live/volatile -> Tier 0 (Online-only)
- Data is reference/historical -> Tier 1 (Read cache fallback)
- Users need to perform actions offline -> Tier 2 (Offline write queue)
- App must work fully offline -> Tier 3 (Full sync)
Tier 0: No Setup Required
Section titled “Tier 0: No Setup Required”Default behavior. Standard Convertor pipeline with no offline handling.
Tier 1: Read Cache Fallback
Section titled “Tier 1: Read Cache Fallback”1. Ensure Ferry Cache is Configured
Section titled “1. Ensure Ferry Cache is Configured”Ferry’s cache is enabled by default in the GQL client setup:
@riverpodGqlClient gqlClient(Ref ref) { final cache = Cache(); // Ferry's normalized cache // ... link setup return GqlClient(link: link, cache: cache);}2. Add Connectivity Awareness
Section titled “2. Add Connectivity Awareness”@riverpodStream<bool> connectivity(Ref ref) { return Connectivity().onConnectivityChanged.map( (result) => result != ConnectivityResult.none, );}3. Show Offline Indicator in UI
Section titled “3. Show Offline Indicator in UI”Widget build(BuildContext context, WidgetRef ref) { final isOnline = ref.watch(connectivityProvider).value ?? true; final dataAsync = ref.watch(featureNotifierProvider);
return Column( children: [ if (!isOnline) const MaterialBanner( content: Text('You are offline. Showing cached data.'), actions: [SizedBox.shrink()], ), Expanded(child: dataAsync.when(/* ... */)), ], );}Tier 2: Offline Write Queue
Section titled “Tier 2: Offline Write Queue”1. Complete Tier 1 Setup
Section titled “1. Complete Tier 1 Setup”2. Create Queued Mutation Model
Section titled “2. Create Queued Mutation Model”@freezedclass QueuedMutation with _$QueuedMutation { const factory QueuedMutation({ required String id, required String type, required Map<String, dynamic> params, required DateTime createdAt, @Default(0) int retryCount, }) = _QueuedMutation;
factory QueuedMutation.fromJson(Map<String, dynamic> json) => _$QueuedMutationFromJson(json);}3. Implement Offline Queue
Section titled “3. Implement Offline Queue”@riverpodclass OfflineQueue extends _$OfflineQueue { @override Future<List<QueuedMutation>> build() async { final cached = await ref.read(cachingServiceProvider) .read<List<Map<String, dynamic>>>('offline_queue'); return cached?.map(QueuedMutation.fromJson).toList() ?? []; }
Future<void> enqueue(QueuedMutation mutation) async { final current = state.value ?? []; final updated = [...current, mutation]; await _persist(updated); state = AsyncData(updated); }
Future<void> _persist(List<QueuedMutation> queue) async { await ref.read(cachingServiceProvider).store( key: 'offline_queue', value: queue.map((m) => m.toJson()).toList(), ); }}4. Implement Sync Trigger
Section titled “4. Implement Sync Trigger”@riverpodclass OfflineSyncService extends _$OfflineSyncService { @override void build() { // Listen for connectivity changes ref.listen(connectivityProvider, (_, isOnline) { if (isOnline.value == true) { _syncQueue(); } }); }
Future<void> _syncQueue() async { final queue = await ref.read(offlineQueueProvider.future); for (final mutation in queue) { try { await _executeMutation(mutation); // Remove from queue on success } catch (e) { break; // Stop on first failure } } }}Tier 3: Full Sync
Section titled “Tier 3: Full Sync”Tier 3 requires significant additional infrastructure beyond the scope of this quick-start guide. Key components needed:
- Local database (Drift, Isar, or Hive)
- Sync engine with version tracking
- Conflict resolution strategy per entity
- Background sync service
See the Offline Tiers Reference for architectural guidance on Tier 3 implementation.