# Phase 2.4: Mobile App (React Native) ## Overview **Phase 2.4** extends the platform to mobile devices with a **native React Native application** for iOS and Android, featuring: - 📱 Native mobile UI (iOS/Android) - 💬 Real-time chat with push notifications - 📋 Care plan management - 📊 Health indicator tracking - 🔐 Biometric authentication - 📴 Offline-first architecture - 🔄 Automatic sync when online **Prerequisites:** - Phase 2.1 (PostgreSQL Database) - Phase 2.2 (Analytics) - Phase 2.3 (FHIR Integration) - optional --- ## Architecture ``` ┌─────────────────────────────────────────┐ │ iOS App (Swift/Objective-C) │ ├─────────────────────────────────────────┤ │ React Native Bridge │ ├─────────────────────────────────────────┤ │ JavaScript/TypeScript Layer │ │ (App.tsx, screens, components) │ ├─────────────────────────────────────────┤ │ Redux State Management │ │ (Store, reducers, actions) │ ├─────────────────────────────────────────┤ │ API Client & Sync Engine │ ├─────────────────────────────────────────┤ │ Local Storage (SQLite/AsyncStorage) │ ├─────────────────────────────────────────┤ │ Backend APIs │ │ (app.py, app_phase2.py) │ ├─────────────────────────────────────────┤ │ PostgreSQL Database │ └─────────────────────────────────────────┘ ``` --- ## Setup Instructions ### 1. Environment Setup ```bash # Install Node.js and npm (if not already installed) node --version # Should be 18+ npm --version # Should be 8+ # Install React Native CLI npm install -g react-native-cli # Install iOS/Android requirements # macOS: Install Xcode and CocoaPods xcode-select --install sudo gem install cocoapods # Android: Install Android Studio # https://developer.android.com/studio ``` ### 2. Create Mobile Project ```bash # Navigate to mobile directory cd mobile # Install dependencies npm install # Install Pods (iOS) cd ios && pod install && cd .. ``` ### 3. Configure Backend Connection ```bash # Create .env file cat > .env << 'EOF' REACT_APP_API_URL=https://your-backend.com REACT_APP_API_TIMEOUT=10000 REACT_APP_FIREBASE_API_KEY=your_firebase_key REACT_APP_FIREBASE_PROJECT_ID=your_project_id REACT_APP_ENABLE_OFFLINE=true REACT_APP_ENABLE_PUSH_NOTIFICATIONS=true EOF ``` ### 4. Run on Simulator/Device ```bash # iOS Simulator npm run ios # or react-native run-ios # Android Emulator npm run android # or react-native run-android # Device (iOS) react-native run-ios --device "Device Name" # Device (Android) react-native run-android ``` --- ## Core Features ### 1. **Authentication** ```typescript // Login with credentials import { authService } from './services/authService'; const login = async (username: string, password: string) => { try { const response = await authService.login(username, password); await AsyncStorage.setItem('authToken', response.token); dispatch(setAuth(response.user)); } catch (error) { console.error('Login failed:', error); } }; // Biometric login (iOS/Android) import * as LocalAuthentication from 'react-native-local-authentication'; const biometricLogin = async () => { try { const compatible = await LocalAuthentication.hasHardwareAsync(); if (compatible) { await LocalAuthentication.authenticateAsync({ disableDeviceFallback: false, }); // Load token from secure storage } } catch (error) { console.error('Biometric auth failed:', error); } }; ``` ### 2. **Real-time Chat** ```typescript // Send message import { chatService } from './services/chatService'; const sendMessage = async (text: string) => { try { const message = { id: Date.now(), text, sender: 'user', timestamp: new Date(), synced: false, }; // Save locally first dispatch(addMessage(message)); // Sync when online if (isOnline) { const response = await chatService.sendMessage(text); dispatch(updateMessage({ id: message.id, ...response })); } } catch (error) { console.error('Message send failed:', error); } }; // Listen for push notifications messaging().onMessage(async (remoteMessage) => { console.log('Notification received:', remoteMessage); dispatch(addNotification(remoteMessage)); }); ``` ### 3. **Offline-First Sync** ```typescript // Redux Persist for offline support import { persistStore, persistReducer } from 'redux-persist'; import AsyncStorage from '@react-native-async-storage/async-storage'; const persistConfig = { key: 'root', storage: AsyncStorage, whitelist: ['chat', 'user', 'patients'], }; const persistedReducer = persistReducer(persistConfig, rootReducer); // Sync when connection restored NetInfo.addEventListener(state => { if (state.isConnected && hasPendingChanges) { syncWithServer(); } }); ``` ### 4. **Care Plan Management** ```typescript // View patient care plans import { carePlanService } from './services/carePlanService'; const loadCarePlans = async (patientId: string) => { try { let plans = await AsyncStorage.getItem(`carePlans_${patientId}`); if (!plans || isStale(plans)) { plans = await carePlanService.getCarePlans(patientId); await AsyncStorage.setItem( `carePlans_${patientId}`, JSON.stringify(plans) ); } dispatch(setCarePlans(JSON.parse(plans))); } catch (error) { console.error('Load care plans failed:', error); } }; ``` ### 5. **Health Indicators** ```typescript // Track vital signs import { healthService } from './services/healthService'; const recordVitals = async (vitals: VitalsData) => { try { const observation = { bloodPressure: vitals.bp, temperature: vitals.temp, oxygenSaturation: vitals.o2, heartRate: vitals.hr, recordedAt: new Date(), synced: false, }; // Save locally await AsyncStorage.setItem( 'lastVitals', JSON.stringify(observation) ); // Sync to server if (isOnline) { await healthService.recordObservation(observation); } } catch (error) { console.error('Record vitals failed:', error); } }; ``` --- ## Project Structure ``` mobile/ ├── App.tsx # Main app entry ├── package.json # Dependencies ├── tsconfig.json # TypeScript config ├── babel.config.js # Babel config ├── app.json # App metadata ├── index.js # React Native entry │ ├── src/ │ ├── screens/ │ │ ├── LoginScreen.tsx # Authentication │ │ ├── ChatScreen.tsx # Chat interface │ │ ├── CarePlanScreen.tsx # Care plan view │ │ ├── ProblemsScreen.tsx # Problems/diagnoses │ │ ├── InterventionsScreen.tsx # Interventions │ │ ├── HealthIndicatorsScreen.tsx │ │ ├── SettingsScreen.tsx # User settings │ │ └── PatientDetailsScreen.tsx # Patient info │ │ │ ├── components/ │ │ ├── ChatBubble.tsx # Message component │ │ ├── CarePlanCard.tsx # Care plan card │ │ ├── HealthIndicator.tsx # Vital signs │ │ ├── LoadingSpinner.tsx # Loading UI │ │ └── ErrorBoundary.tsx # Error handling │ │ │ ├── services/ │ │ ├── authService.ts # Authentication │ │ ├── chatService.ts # Chat API │ │ ├── carePlanService.ts # Care plans │ │ ├── healthService.ts # Health data │ │ ├── syncService.ts # Offline sync │ │ └── api.ts # HTTP client │ │ │ ├── store/ │ │ ├── index.ts # Store setup │ │ ├── rootReducer.ts # Root reducer │ │ ├── slices/ │ │ │ ├── authSlice.ts # Auth state │ │ │ ├── chatSlice.ts # Chat state │ │ │ ├── patientSlice.ts # Patient state │ │ │ └── uiSlice.ts # UI state │ │ └── hooks.ts # Redux hooks │ │ │ ├── types/ │ │ ├── index.ts # TypeScript types │ │ ├── auth.ts # Auth types │ │ ├── chat.ts # Chat types │ │ └── patient.ts # Patient types │ │ │ ├── utils/ │ │ ├── formatters.ts # Data formatting │ │ ├── validators.ts # Input validation │ │ ├── storage.ts # Local storage │ │ └── networkStatus.ts # Network detection │ │ │ ├── styles/ │ │ ├── colors.ts # Theme colors │ │ ├── fonts.ts # Typography │ │ └── spacing.ts # Spacing scale │ │ │ └── config/ │ ├── constants.ts # App constants │ ├── environment.ts # Env vars │ └── logger.ts # Logging │ ├── ios/ # iOS native code │ ├── Podfile # iOS dependencies │ └── NursingValidator/ # iOS app files │ ├── android/ # Android native code │ ├── build.gradle # Android config │ └── app/ # Android app files │ └── __tests__/ ├── screens/ # Screen tests ├── components/ # Component tests ├── services/ # Service tests └── store/ # Redux tests ``` --- ## Key Packages ### Navigation - `@react-navigation/native` - Navigation framework - `@react-navigation/bottom-tabs` - Tab navigator - `@react-navigation/stack` - Stack navigator ### State Management - `redux` - State container - `@reduxjs/toolkit` - Redux utilities - `redux-persist` - Persist state ### UI Components - `react-native-vector-icons` - Icons - `react-native-svg` - SVG support - `react-native-gesture-handler` - Gestures ### Firebase - `@react-native-firebase/app` - Firebase core - `@react-native-firebase/messaging` - Push notifications - `@react-native-firebase/analytics` - Analytics ### Storage - `@react-native-async-storage/async-storage` - Key-value storage - `react-native-sqlite-storage` - Local database ### Forms - `formik` - Form management - `yup` - Validation schema --- ## Push Notifications Setup ### Firebase Cloud Messaging (FCM) ```bash # 1. Create Firebase project # https://firebase.google.com/ # 2. Download google-services.json (Android) # Place in: android/app/google-services.json # 3. Download GoogleService-Info.plist (iOS) # Place in: ios/GoogleService-Info.plist # 4. Install React Native Firebase npm install @react-native-firebase/app @react-native-firebase/messaging # 5. Link native modules cd ios && pod install && cd .. ``` ### Push Notification Handler ```typescript import messaging from '@react-native-firebase/messaging'; // Handle foreground messages messaging().onMessage(async (remoteMessage) => { console.log('Foreground message:', remoteMessage); // Update UI with notification }); // Handle background/killed messages messaging().onNotificationOpenedApp((remoteMessage) => { console.log('App opened from notification:', remoteMessage); // Navigate to appropriate screen }); // Get initial notification (app closed) messaging() .getInitialNotification() .then((remoteMessage) => { if (remoteMessage) { console.log('App opened from quit state:', remoteMessage); } }); ``` --- ## Testing ### Unit Tests ```bash # Run tests npm test # With coverage npm test -- --coverage ``` ### E2E Tests ```bash # Detox setup npm install -g detox-cli detox build-framework-cache # Run E2E tests detox test e2e --configuration ios.sim.debug ``` ### Sample Test ```typescript import { render, screen, fireEvent } from '@testing-library/react-native'; import LoginScreen from '../screens/LoginScreen'; describe('LoginScreen', () => { it('renders login form', () => { render(); expect(screen.getByPlaceholderText('Username')).toBeTruthy(); }); it('submits login form', async () => { const { getByPlaceholderText, getByText } = render(); fireEvent.changeText(getByPlaceholderText('Username'), 'nurse'); fireEvent.changeText(getByPlaceholderText('Password'), 'nurse2025'); fireEvent.press(getByText('Login')); // Assert navigation or API call }); }); ``` --- ## Troubleshooting ### Build Errors ```bash # Clear cache and rebuild rm -rf node_modules rm -rf Pods npm install cd ios && pod install && cd .. npm run ios ``` ### Simulator Issues ```bash # Reset simulator xcrun simctl erase all # Rebuild and run npm run ios ``` ### Android Issues ```bash # Clear Gradle cache cd android && ./gradlew clean && cd .. # Rebuild npm run android ``` --- ## Backend API Integration ### API Endpoints Required ``` POST /api/v1/auth/login - User authentication POST /api/v1/auth/logout - User logout GET /api/v1/user/profile - Get user profile POST /api/v1/chat/send - Send message GET /api/v1/chat/history - Get messages GET /api/v1/patients - List patients GET /api/v1/patients/{id} - Get patient GET /api/v1/patients/{id}/plans - Get care plans GET /api/v1/health/vitals - Get vital signs POST /api/v1/health/vitals - Record vitals POST /api/v1/notifications/token - Register push token ``` ### API Client Example ```typescript // src/services/api.ts import axios from 'axios'; import AsyncStorage from '@react-native-async-storage/async-storage'; const api = axios.create({ baseURL: process.env.REACT_APP_API_URL, timeout: 10000, }); // Add auth token to requests api.interceptors.request.use(async (config) => { const token = await AsyncStorage.getItem('authToken'); if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }); // Handle token expiry api.interceptors.response.use( (response) => response, async (error) => { if (error.response?.status === 401) { // Token expired, redirect to login await AsyncStorage.removeItem('authToken'); // Navigation.navigate('Login'); } return Promise.reject(error); } ); export default api; ``` --- ## Performance Optimization ### Image Optimization ```typescript import FastImage from 'react-native-fast-image'; // Use FastImage instead of Image ``` ### FlatList Performance ```typescript // Optimize list rendering } keyExtractor={(item) => item.id} removeClippedSubviews={true} maxToRenderPerBatch={10} updateCellsBatchingPeriod={50} initialNumToRender={10} onEndReachedThreshold={0.5} onEndReached={loadMoreMessages} /> ``` ### Code Splitting ```typescript // Lazy load screens const CarePlanScreen = React.lazy(() => import('./CarePlanScreen')); const ProblemsScreen = React.lazy(() => import('./ProblemsScreen')); ``` --- ## Deployment ### iOS App Store ```bash # Build release npm run build:ios # Submit to App Store Connect # https://appstoreconnect.apple.com ``` ### Google Play Store ```bash # Build release APK npm run build:android # Upload to Google Play Console # https://play.google.com/console ``` --- ## Next Steps 1. Set up development environment (Node.js, Xcode, Android Studio) 2. Clone mobile project 3. Configure backend connection 4. Run on simulator/device 5. Implement push notifications 6. Add biometric authentication 7. Test offline sync 8. Deploy to app stores --- ## Files Provided **New Files:** - `mobile/package.json` - Dependencies - `mobile/App.tsx` - Main app component - `mobile/src/screens/` - Screen components (skeleton) - `mobile/src/services/` - API services (skeleton) - `mobile/src/store/` - Redux state (skeleton) **Documentation:** - `PHASE2_MOBILE.md` (this file) --- ## References - [React Native Docs](https://reactnative.dev/) - [React Navigation](https://reactnavigation.org/) - [Redux Toolkit](https://redux-toolkit.js.org/) - [Firebase React Native](https://rnfirebase.io/) - [React Native Firebase Messaging](https://rnfirebase.io/messaging/overview) --- *Phase 2.4 Mobile App - November 29, 2025*