# Offline Mode Graceful Degradation This document describes the offline mode implementation for the WellNuo app. ## Overview The WellNuo app uses an **API-first architecture** with no local storage for business data. However, it provides graceful degradation when the network is unavailable through: 1. **Network detection** - Real-time monitoring of connectivity status 2. **Offline-aware API wrapper** - Automatic detection and user-friendly error messages 3. **UI components** - Visual feedback when offline 4. **Retry logic** - Automatic retries with exponential backoff ## Architecture ``` ┌─────────────────────────────────────────────────────────────┐ │ Application Layer │ │ │ │ Components use: │ │ - useOfflineAwareData hook │ │ - useOfflineAwareMutation hook │ │ - OfflineBanner component │ └──────────────────────┬───────────────────────────────────────┘ │ ┌──────────────────────▼───────────────────────────────────────┐ │ Offline-Aware API Layer │ │ │ │ offlineAwareApi wraps all API methods with: │ │ - Network status checks │ │ - Retry logic │ │ - User-friendly error messages │ └──────────────────────┬───────────────────────────────────────┘ │ ┌──────────────────────▼───────────────────────────────────────┐ │ Network Detection Layer │ │ │ │ - useNetworkStatus hook (React) │ │ - getNetworkStatus() (async function) │ │ - isOnline() (async function) │ │ - retryWithBackoff() (utility) │ └──────────────────────┬───────────────────────────────────────┘ │ ┌──────────────────────▼───────────────────────────────────────┐ │ @react-native-community/netinfo │ │ │ │ - Native network connectivity detection │ │ - Real-time change notifications │ └──────────────────────────────────────────────────────────────┘ ``` ## Key Components ### 1. Network Detection (`utils/networkStatus.ts`) **Functions:** - `getNetworkStatus(): Promise` - Get current network status - `isOnline(): Promise` - Check if device is online - `retryWithBackoff(operation, config): Promise` - Retry with exponential backoff **Hooks:** - `useNetworkStatus()` - React hook for real-time network status ```typescript const { isOnline, isOffline, status } = useNetworkStatus(); ``` - `useOnlineOnly()` - Execute callback only when online ```typescript const executeOnline = useOnlineOnly(); executeOnline(() => api.saveBeneficiary(data)); ``` - `useRetry(config)` - Hook for retrying operations ```typescript const retry = useRetry({ maxAttempts: 3, delayMs: 1000 }); const data = await retry(() => api.getAllBeneficiaries()); ``` ### 2. Offline-Aware API (`services/offlineAwareApi.ts`) Wraps the main API service with offline detection and graceful error handling. **Usage:** ```typescript import { offlineAwareApi } from '@/services/offlineAwareApi'; // Instead of: // const response = await api.getAllBeneficiaries(); // Use: const response = await offlineAwareApi.getAllBeneficiaries(); if (!response.ok) { // response.error.message contains user-friendly message Alert.alert('Error', response.error.message); } ``` **Features:** - Automatic network detection before API calls - Custom offline messages for write operations - Retry logic for read operations - Consistent error format **Available Methods:** All methods from `api.ts` are wrapped, including: - `getAllBeneficiaries()` - with retry - `getWellNuoBeneficiary(id)` - with retry - `createBeneficiary(data)` - with offline message - `updateWellNuoBeneficiary(id, updates)` - with offline message - `deleteBeneficiary(id)` - with offline message - And all other API methods... ### 3. UI Components #### OfflineBanner (`components/OfflineBanner.tsx`) Displays a banner at the top/bottom of the screen when offline. **Usage:** ```typescript import { OfflineBanner } from '@/components/OfflineBanner'; function MyScreen() { return ( {/* Rest of content */} ); } ``` **Props:** - `message?: string` - Custom message (default: "No internet connection") - `position?: 'top' | 'bottom'` - Banner position (default: "top") - `backgroundColor?: string` - Background color (default: "#FF3B30") - `textColor?: string` - Text color (default: "#FFFFFF") - `height?: number` - Banner height (default: 40) #### InlineOfflineBanner Non-animated inline version for use within component trees. ```typescript import { InlineOfflineBanner } from '@/components/OfflineBanner'; function MyComponent() { return ( {/* Content */} ); } ``` ### 4. Data Fetching Hooks #### useOfflineAwareData Custom hook for data fetching with offline handling. **Usage:** ```typescript import { useOfflineAwareData } from '@/hooks/useOfflineAwareData'; import { offlineAwareApi } from '@/services/offlineAwareApi'; function BeneficiariesList() { const { data: beneficiaries, loading, error, refetch, refetching, errorMessage, isOfflineError, } = useOfflineAwareData( () => offlineAwareApi.getAllBeneficiaries(), [], // dependencies { refetchOnReconnect: true, // Auto-refetch when back online pollInterval: 0, // Poll interval in ms (0 = disabled) } ); if (loading) return ; if (error) return ; return ( } /> ); } ``` #### useOfflineAwareMutation Hook for mutations (create, update, delete) with offline handling. **Usage:** ```typescript import { useOfflineAwareMutation } from '@/hooks/useOfflineAwareData'; function CreateBeneficiaryForm() { const { mutate, loading, errorMessage } = useOfflineAwareMutation( (data) => offlineAwareApi.createBeneficiary(data), { onSuccess: (beneficiary) => { Alert.alert('Success', `Added ${beneficiary.name}`); }, onError: (error) => { Alert.alert('Error', errorMessage); }, } ); const handleSubmit = async () => { await mutate({ name: 'John Doe' }); }; return ( Add Beneficiary ); } ``` ## Error Messages The offline-aware API provides user-friendly error messages: | Error Type | User Message | |------------|-------------| | Offline | "No internet connection. Please check your network and try again." | | Timeout | "Request timed out. Please try again." | | Unreachable | "Unable to reach the server. Please try again later." | | Generic Network | "Network error occurred. Please check your connection." | Custom messages can be provided for specific operations: ```typescript const response = await withOfflineCheck( () => api.createBeneficiary(data), { offlineMessage: 'Cannot add beneficiary while offline' } ); ``` ## Retry Logic Operations can be retried automatically with exponential backoff: ```typescript import { retryWithBackoff, DEFAULT_RETRY_CONFIG } from '@/utils/networkStatus'; const data = await retryWithBackoff( () => api.getAllBeneficiaries(), { maxAttempts: 3, // Max retry attempts delayMs: 1000, // Initial delay (ms) backoffMultiplier: 2, // Exponential backoff factor } ); ``` **Default config:** - Max attempts: 3 - Initial delay: 1000ms - Backoff multiplier: 2 - Retry delays: 1s → 2s → 4s **Smart retry:** - Only retries if network is available - Stops immediately if network goes offline - Uses exponential backoff to avoid overwhelming the server ## Testing Tests are located in `__tests__/offline/`: ```bash # Run offline mode tests npm test -- __tests__/offline # Run all tests npm test ``` **Test coverage:** - Network status detection - Offline-aware API wrapper - Error message generation - Retry logic with backoff ## Best Practices ### 1. Always Use Offline-Aware API ❌ **Don't:** ```typescript const response = await api.getAllBeneficiaries(); if (!response.ok) { Alert.alert('Error', response.error?.message); } ``` ✅ **Do:** ```typescript const response = await offlineAwareApi.getAllBeneficiaries(); if (!response.ok) { Alert.alert('Error', response.error?.message); // User-friendly message } ``` ### 2. Use Hooks for Data Fetching ❌ **Don't:** ```typescript const [data, setData] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { api.getAllBeneficiaries().then(response => { if (response.ok) setData(response.data); setLoading(false); }); }, []); ``` ✅ **Do:** ```typescript const { data, loading, error, refetch } = useOfflineAwareData( () => offlineAwareApi.getAllBeneficiaries(), [] ); ``` ### 3. Show Offline Banner ```typescript function MyScreen() { return ( {/* Content */} ); } ``` ### 4. Handle Errors Gracefully ```typescript const { data, loading, error, errorMessage, isOfflineError } = useOfflineAwareData(...); if (error) { return ( {isOfflineError ? 'You\'re Offline' : 'Error'} {errorMessage}