Skip to content

πŸ“΄ Offline Sync & Feature Flags ​

Offline Sync (providers/sync-provider.tsx) ​

SyncProvider monitors network state and app foreground transitions to automatically flush any notes created while offline.

Sync Triggers ​

TriggerDelay
Network reconnects1.5s debounce
App comes to foregroundImmediate
New note added to offline queueImmediate

Retry Strategy ​

Failed uploads use exponential backoff: 5 retries with delays of 5s, 25s, 125s, 625s, 3125s (max ~52 min total). After 5 failures, the note is marked failed and an alert is shown to the user.

Reading Sync State ​

tsx
import { useSyncContext } from '@/providers/sync-provider';

const { syncState, failedCount, retryAll } = useSyncContext();

// syncState: 'idle' | 'syncing' | 'error'
// failedCount: number of permanently failed notes
// retryAll(): manually retry all failed notes

Offline Queue (hooks/use-offline-queue.ts) ​

Notes created without connectivity are saved to MMKV (synchronous key-value storage, faster than AsyncStorage).

The offlineNotes array is shaped as Draft[] so it can be rendered in the same list component as synced notes.

ts
const { offlineNotes, addToQueue, removeFromQueue } = useOfflineQueue();

// Add a note to the queue (called automatically by useCreateNote on network error)
addToQueue(draft);

// Remove after successful sync
removeFromQueue(draft.id);

Feature Flags (lib/feature-flags/) ​

Feature flags are backed by Firebase Remote Config. All flags are boolean.

Flag Registry (lib/feature-flags/flags.ts) ​

Every new flag must be added here before use. The key must exactly match the key in the Firebase Remote Config console.

ts
export const FEATURE_FLAG_DEFAULTS = {
  example_new_map_clustering: false,
  // add new flags here
} as const;

export type FeatureFlagName = keyof typeof FEATURE_FLAG_DEFAULTS;

Defaults must be "safe off" β€” the app must work correctly when the flag is false.

Initialization (lib/feature-flags/remote-config.ts) ​

initRemoteConfig() is called once on app start. It fetches and activates remote values. If it fails (e.g. no network), it silently falls back to the in-code defaults β€” it never throws.

Cache interval:

  • Production: 1 hour
  • Dev / Staging: 0 (always fetch fresh)

Reading Flags ​

tsx
import { useFeatureFlag } from '@/hooks/use-feature-flag';

// Single flag (preferred)
const isClusteringEnabled = useFeatureFlag('example_new_map_clustering');

// Full flag map
import { useFeatureFlags } from '@/hooks/use-feature-flags';
const flags = useFeatureFlags();

TypeScript will error on unknown flag names.

Adding a New Flag β€” Checklist ​

  1. Add the key + default to FEATURE_FLAG_DEFAULTS in lib/feature-flags/flags.ts
  2. Add the key in the Firebase Remote Config console with the desired production value
  3. Use useFeatureFlag('your_flag_key') in the component
  4. Guard the new behavior: if (!isEnabled) return <OldBehavior />