Files
maternal-app/docs/mobile-app-best-practices.md
andupetcu a91a7b009a
Some checks failed
Backend CI/CD Pipeline / Lint and Test Backend (push) Has been cancelled
CI/CD Pipeline / Lint and Test (push) Has been cancelled
Backend CI/CD Pipeline / E2E Tests Backend (push) Has been cancelled
Backend CI/CD Pipeline / Build Backend Application (push) Has been cancelled
Backend CI/CD Pipeline / Performance Testing (push) Has been cancelled
CI/CD Pipeline / E2E Tests (push) Has been cancelled
CI/CD Pipeline / Build Application (push) Has been cancelled
Add backend with analytics, notifications, and enhanced features
Backend:
- Complete NestJS backend implementation with comprehensive features
- Analytics: Weekly/monthly reports with PDF/CSV export
- Smart notifications: Persistent notifications with milestones and anomaly detection
- AI safety: Medical disclaimer triggers and prompt injection protection
- COPPA/GDPR compliance: Full audit logging system

Frontend:
- Updated settings page and analytics components
- API integration improvements

Docs:
- Added implementation gaps tracking
- Azure OpenAI integration documentation
- Testing and post-launch summaries

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-01 15:22:50 +03:00

976 lines
22 KiB
Markdown

# Mobile App Best Practices for Future Implementation
## React Native Implementation Readiness Guide
---
## Overview
This document outlines best practices, architectural patterns, and implementation guidelines for building the native mobile apps (iOS & Android) using React Native. The current web implementation provides a solid foundation that can be leveraged for the mobile apps.
### Current Implementation Status
-**Web App (maternal-web)**: Fully implemented with Next.js 14
-**Backend API (maternal-app-backend)**: Complete with REST + WebSocket
-**Mobile Apps**: Not yet implemented (planned)
### Technology Stack for Mobile
```javascript
{
"react-native": "^0.73.0",
"expo": "~50.0.0",
"@react-navigation/native": "^6.1.0",
"@react-navigation/stack": "^6.3.0",
"react-native-paper": "^5.12.0",
"redux-toolkit": "^2.0.0",
"react-native-reanimated": "^3.6.0",
"expo-secure-store": "~12.8.0",
"expo-notifications": "~0.27.0"
}
```
---
## Architecture Principles
### 1. Code Reusability Between Web and Mobile
**Shared Business Logic**
```typescript
// ✅ GOOD: Platform-agnostic business logic
// libs/shared/src/services/activityService.ts
export class ActivityService {
async logActivity(data: ActivityData): Promise<Activity> {
// Platform-independent logic
return this.apiClient.post('/activities', data);
}
}
// Can be used in both web and mobile
```
**Platform-Specific UI**
```typescript
// ❌ BAD: Mixing UI and logic
function TrackingButton() {
const [activity, setActivity] = useState();
// Business logic mixed with UI
}
// ✅ GOOD: Separate concerns
// hooks/useActivityTracking.ts
export function useActivityTracking() {
// Reusable logic
}
// web/components/TrackingButton.tsx
// mobile/components/TrackingButton.tsx
// Different UI, same logic via hook
```
**Recommended Project Structure**
```
maternal-app-monorepo/
├── apps/
│ ├── web/ # Next.js web app (existing)
│ ├── mobile/ # React Native mobile app (future)
│ └── backend/ # NestJS API (existing)
├── packages/
│ ├── shared/ # Shared between web & mobile
│ │ ├── api-client/ # API communication
│ │ ├── state/ # Redux store & slices
│ │ ├── hooks/ # Custom React hooks
│ │ ├── utils/ # Utilities
│ │ └── types/ # TypeScript definitions
│ ├── ui-components/ # Platform-specific UI
│ │ ├── web/
│ │ └── mobile/
│ └── constants/ # Shared constants
└── tools/ # Build tools & scripts
```
---
## Mobile-Specific Features
### 1. Offline-First Architecture
**Local Database: SQLite**
```typescript
// Mobile: Use SQLite for offline storage
import * as SQLite from 'expo-sqlite';
const db = SQLite.openDatabase('maternal.db');
// Sync queue for offline operations
interface SyncQueueItem {
id: string;
operation: 'CREATE' | 'UPDATE' | 'DELETE';
entity: 'activity' | 'child' | 'family';
data: any;
timestamp: Date;
retryCount: number;
}
// Auto-sync when connection restored
export class OfflineSyncService {
async syncPendingChanges() {
const pendingItems = await this.getSyncQueue();
for (const item of pendingItems) {
try {
await this.syncItem(item);
await this.removefromQueue(item.id);
} catch (error) {
await this.incrementRetryCount(item.id);
}
}
}
}
```
**Conflict Resolution**
```typescript
// Last-write-wins with timestamp comparison
export class ConflictResolver {
resolve(local: Activity, remote: Activity): Activity {
const localTime = new Date(local.updatedAt);
const remoteTime = new Date(remote.updatedAt);
// Use latest version
return localTime > remoteTime ? local : remote;
}
}
```
### 2. Push Notifications
**Expo Notifications Setup**
```typescript
import * as Notifications from 'expo-notifications';
import * as Device from 'expo-device';
export class NotificationService {
async registerForPushNotifications() {
if (!Device.isDevice) {
return null;
}
const { status: existingStatus } =
await Notifications.getPermissionsAsync();
let finalStatus = existingStatus;
if (existingStatus !== 'granted') {
const { status } =
await Notifications.requestPermissionsAsync();
finalStatus = status;
}
if (finalStatus !== 'granted') {
return null;
}
const token = (
await Notifications.getExpoPushTokenAsync({
projectId: 'your-expo-project-id'
})
).data;
// Send token to backend
await this.apiClient.post('/users/push-token', { token });
return token;
}
// Configure notification behavior
configureNotifications() {
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldPlaySound: true,
shouldSetBadge: true,
}),
});
}
}
```
**Notification Categories**
```typescript
// Backend: Define notification types
export enum NotificationType {
FAMILY_UPDATE = 'family_update',
ACTIVITY_REMINDER = 'activity_reminder',
MILESTONE_REACHED = 'milestone_reached',
AI_INSIGHT = 'ai_insight',
SYNC_COMPLETE = 'sync_complete',
}
// Mobile: Handle notification tap
Notifications.addNotificationResponseReceivedListener(response => {
const { type, data } = response.notification.request.content;
switch (type) {
case NotificationType.FAMILY_UPDATE:
navigation.navigate('Family', { familyId: data.familyId });
break;
case NotificationType.ACTIVITY_REMINDER:
navigation.navigate('Track', { type: data.activityType });
break;
// ... handle other types
}
});
```
### 3. Biometric Authentication
**Face ID / Touch ID / Fingerprint**
```typescript
import * as LocalAuthentication from 'expo-local-authentication';
import * as SecureStore from 'expo-secure-store';
export class BiometricAuthService {
async isBiometricAvailable(): Promise<boolean> {
const compatible = await LocalAuthentication.hasHardwareAsync();
const enrolled = await LocalAuthentication.isEnrolledAsync();
return compatible && enrolled;
}
async authenticateWithBiometrics(): Promise<boolean> {
const result = await LocalAuthentication.authenticateAsync({
promptMessage: 'Authenticate to access Maternal App',
fallbackLabel: 'Use passcode',
});
return result.success;
}
async enableBiometricLogin(userId: string, token: string) {
// Store refresh token securely
await SecureStore.setItemAsync(
`auth_token_${userId}`,
token,
{
keychainAccessible:
SecureStore.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
}
);
// Enable biometric flag
await SecureStore.setItemAsync(
'biometric_enabled',
'true'
);
}
async loginWithBiometrics(): Promise<string | null> {
const authenticated = await this.authenticateWithBiometrics();
if (!authenticated) {
return null;
}
// Retrieve stored token
const userId = await SecureStore.getItemAsync('current_user_id');
const token = await SecureStore.getItemAsync(`auth_token_${userId}`);
return token;
}
}
```
### 4. Voice Input (Whisper)
**React Native Voice**
```typescript
import Voice from '@react-native-voice/voice';
export class VoiceInputService {
constructor() {
Voice.onSpeechResults = this.onSpeechResults;
Voice.onSpeechError = this.onSpeechError;
}
async startListening() {
try {
await Voice.start('en-US');
} catch (error) {
console.error('Voice start error:', error);
}
}
async stopListening() {
try {
await Voice.stop();
} catch (error) {
console.error('Voice stop error:', error);
}
}
onSpeechResults = (event: any) => {
const transcript = event.value[0];
// Send to backend for processing with Whisper
this.processTranscript(transcript);
};
onSpeechError = (event: any) => {
console.error('Speech error:', event.error);
};
async processTranscript(transcript: string) {
// Send to backend Whisper API
const response = await fetch('/api/v1/voice/transcribe', {
method: 'POST',
body: JSON.stringify({ transcript }),
});
const { activityData } = await response.json();
return activityData;
}
}
```
### 5. Camera & Photo Upload
**Expo Image Picker**
```typescript
import * as ImagePicker from 'expo-image-picker';
export class PhotoService {
async requestPermissions() {
const { status } =
await ImagePicker.requestMediaLibraryPermissionsAsync();
if (status !== 'granted') {
Alert.alert(
'Permission needed',
'Please allow access to photos'
);
return false;
}
return true;
}
async pickImage() {
const hasPermission = await this.requestPermissions();
if (!hasPermission) return null;
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: true,
aspect: [4, 3],
quality: 0.8,
});
if (!result.canceled) {
return result.assets[0].uri;
}
return null;
}
async takePhoto() {
const { status } =
await ImagePicker.requestCameraPermissionsAsync();
if (status !== 'granted') {
return null;
}
const result = await ImagePicker.launchCameraAsync({
allowsEditing: true,
aspect: [4, 3],
quality: 0.8,
});
if (!result.canceled) {
return result.assets[0].uri;
}
return null;
}
async uploadPhoto(uri: string, childId: string) {
const formData = new FormData();
formData.append('file', {
uri,
type: 'image/jpeg',
name: 'photo.jpg',
} as any);
formData.append('childId', childId);
const response = await fetch('/api/v1/children/photo', {
method: 'POST',
body: formData,
headers: {
'Content-Type': 'multipart/form-data',
},
});
return response.json();
}
}
```
---
## Performance Optimization
### 1. List Virtualization
**FlatList for Large Datasets**
```typescript
import { FlatList } from 'react-native';
// ✅ GOOD: Virtualized list for activities
<FlatList
data={activities}
renderItem={({ item }) => <ActivityCard activity={item} />}
keyExtractor={(item) => item.id}
// Performance optimizations
removeClippedSubviews={true}
maxToRenderPerBatch={10}
updateCellsBatchingPeriod={50}
initialNumToRender={10}
windowSize={5}
// Pull to refresh
onRefresh={handleRefresh}
refreshing={isRefreshing}
// Infinite scroll
onEndReached={loadMore}
onEndReachedThreshold={0.5}
/>
// ❌ BAD: Rendering all items at once
{activities.map(activity => <ActivityCard key={activity.id} activity={activity} />)}
```
### 2. Image Optimization
**React Native Fast Image**
```typescript
import FastImage from 'react-native-fast-image';
// ✅ GOOD: Optimized image loading
<FastImage
source={{
uri: childPhoto,
priority: FastImage.priority.high,
cache: FastImage.cacheControl.immutable,
}}
style={styles.childPhoto}
resizeMode={FastImage.resizeMode.cover}
/>
// Preload images for better UX
FastImage.preload([
{ uri: photo1 },
{ uri: photo2 },
]);
```
### 3. Animation Performance
**React Native Reanimated 3**
```typescript
import Animated, {
useSharedValue,
useAnimatedStyle,
withSpring,
} from 'react-native-reanimated';
// ✅ GOOD: Run on UI thread
function AnimatedButton() {
const scale = useSharedValue(1);
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ scale: scale.value }],
}));
const handlePress = () => {
scale.value = withSpring(0.95, {}, () => {
scale.value = withSpring(1);
});
};
return (
<Animated.View style={animatedStyle}>
<TouchableOpacity onPress={handlePress}>
<Text>Track Activity</Text>
</TouchableOpacity>
</Animated.View>
);
}
```
### 4. Bundle Size Optimization
**Hermes Engine (for Android)**
```javascript
// android/app/build.gradle
project.ext.react = [
enableHermes: true, // Enable Hermes engine
]
// Results in:
// - Faster startup time
// - Lower memory usage
// - Smaller APK size
```
**Code Splitting**
```typescript
// Lazy load heavy screens
const AIAssistant = lazy(() => import('./screens/AIAssistant'));
const Analytics = lazy(() => import('./screens/Analytics'));
// Use with Suspense
<Suspense fallback={<LoadingSpinner />}>
<AIAssistant />
</Suspense>
```
---
## Testing Strategy for Mobile
### Unit Tests (Jest)
```typescript
import { renderHook, act } from '@testing-library/react-hooks';
import { useActivityTracking } from './useActivityTracking';
describe('useActivityTracking', () => {
it('should track activity successfully', async () => {
const { result } = renderHook(() => useActivityTracking());
await act(async () => {
await result.current.logActivity({
type: 'feeding',
childId: 'child_123',
});
});
expect(result.current.activities).toHaveLength(1);
});
});
```
### Component Tests (React Native Testing Library)
```typescript
import { render, fireEvent } from '@testing-library/react-native';
import { TrackingButton } from './TrackingButton';
describe('TrackingButton', () => {
it('should handle press event', () => {
const onPress = jest.fn();
const { getByText } = render(
<TrackingButton onPress={onPress} />
);
fireEvent.press(getByText('Track Feeding'));
expect(onPress).toHaveBeenCalled();
});
});
```
### E2E Tests (Detox)
```typescript
describe('Activity Tracking Flow', () => {
beforeAll(async () => {
await device.launchApp();
});
it('should log a feeding activity', async () => {
await element(by.id('track-feeding-btn')).tap();
await element(by.id('amount-input')).typeText('120');
await element(by.id('save-btn')).tap();
await expect(element(by.text('Activity saved'))).toBeVisible();
});
});
```
---
## Platform-Specific Considerations
### iOS Specific
**1. App Store Guidelines**
```markdown
- ✅ Submit privacy manifest (PrivacyInfo.xcprivacy)
- ✅ Declare data collection practices
- ✅ Request permissions with clear explanations
- ✅ Support all device sizes (iPhone, iPad)
- ✅ Dark mode support required
```
**2. iOS Permissions**
```xml
<!-- ios/maternal/Info.plist -->
<key>NSCameraUsageDescription</key>
<string>Take photos of your child's milestones</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Save and view photos of your child</string>
<key>NSMicrophoneUsageDescription</key>
<string>Use voice to log activities hands-free</string>
<key>NSFaceIDUsageDescription</key>
<string>Use Face ID for quick and secure login</string>
```
**3. iOS Background Modes**
```xml
<key>UIBackgroundModes</key>
<array>
<string>remote-notification</string>
<string>fetch</string>
</array>
```
### Android Specific
**1. Permissions**
```xml
<!-- android/app/src/main/AndroidManifest.xml -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
```
**2. ProGuard (Code Obfuscation)**
```
# android/app/proguard-rules.pro
-keep class com.maternalapp.** { *; }
-keepclassmembers class * {
@com.facebook.react.uimanager.annotations.ReactProp <methods>;
}
```
**3. App Signing**
```bash
# Generate release keystore
keytool -genkeypair -v -storetype PKCS12 \
-keystore maternal-app-release.keystore \
-alias maternal-app \
-keyalg RSA -keysize 2048 \
-validity 10000
```
---
## Deployment & Distribution
### App Store (iOS)
**1. Build Configuration**
```bash
# Install dependencies
cd ios && pod install
# Build for production
xcodebuild -workspace MaternalApp.xcworkspace \
-scheme MaternalApp \
-configuration Release \
-archivePath MaternalApp.xcarchive \
archive
# Export IPA
xcodebuild -exportArchive \
-archivePath MaternalApp.xcarchive \
-exportPath ./build \
-exportOptionsPlist ExportOptions.plist
```
**2. TestFlight (Beta Testing)**
```bash
# Upload to TestFlight
xcrun altool --upload-app \
--type ios \
--file MaternalApp.ipa \
--username "developer@example.com" \
--password "@keychain:AC_PASSWORD"
```
### Google Play (Android)
**1. Build AAB (Android App Bundle)**
```bash
cd android
./gradlew bundleRelease
# Output: android/app/build/outputs/bundle/release/app-release.aab
```
**2. Internal Testing Track**
```bash
# Upload to Google Play Console
# Use Fastlane or manual upload
```
### Over-the-Air Updates (CodePush)
**Setup for rapid iteration**
```bash
# Install CodePush CLI
npm install -g code-push-cli
# Register app
code-push app add maternal-app-ios ios react-native
code-push app add maternal-app-android android react-native
# Release update
code-push release-react maternal-app-ios ios \
-d Production \
--description "Bug fixes and performance improvements"
```
**Rollback Strategy**
```bash
# Rollback to previous version if issues detected
code-push rollback maternal-app-ios Production
# Monitor adoption rate
code-push deployment ls maternal-app-ios
```
---
## Monitoring & Analytics
### Crash Reporting (Sentry)
```typescript
import * as Sentry from '@sentry/react-native';
Sentry.init({
dsn: 'YOUR_SENTRY_DSN',
environment: __DEV__ ? 'development' : 'production',
tracesSampleRate: 1.0,
});
// Automatic breadcrumbs
Sentry.addBreadcrumb({
category: 'activity',
message: 'User logged feeding activity',
level: 'info',
});
// Custom error context
Sentry.setContext('user', {
id: user.id,
familyId: family.id,
});
```
### Performance Monitoring
```typescript
import * as Sentry from '@sentry/react-native';
// Monitor screen load time
const transaction = Sentry.startTransaction({
name: 'ActivityTrackingScreen',
op: 'navigation',
});
// ... screen loads ...
transaction.finish();
// Monitor specific operations
const span = transaction.startChild({
op: 'api.call',
description: 'Log activity',
});
await logActivity(data);
span.finish();
```
### Usage Analytics
```typescript
// Integrate with backend analytics service
import { Analytics } from '@maternal/shared/analytics';
Analytics.track('Activity Logged', {
type: 'feeding',
method: 'voice',
duration: 15000,
});
Analytics.screen('Activity Tracking');
Analytics.identify(user.id, {
familySize: family.members.length,
childrenCount: children.length,
isPremium: subscription.isPremium,
});
```
---
## Accessibility (WCAG AA Compliance)
### Screen Reader Support
```typescript
import { View, Text, TouchableOpacity } from 'react-native';
<TouchableOpacity
accessible={true}
accessibilityLabel="Log feeding activity"
accessibilityHint="Opens feeding activity tracker"
accessibilityRole="button"
onPress={handlePress}
>
<Text>Track Feeding</Text>
</TouchableOpacity>
```
### Dynamic Font Sizes
```typescript
import { Text, useWindowDimensions } from 'react-native';
// Respect user's font size preferences
<Text
style={{
fontSize: 16,
lineHeight: 24,
}}
allowFontScaling={true}
maxFontSizeMultiplier={2}
>
Activity logged successfully
</Text>
```
### Color Contrast
```typescript
// Ensure WCAG AA compliance (4.5:1 ratio for normal text)
const colors = {
primary: '#FF8B7D', // Coral
primaryText: '#1A1A1A', // Dark text on light background
background: '#FFFFFF',
textOnPrimary: '#FFFFFF', // White text on coral
};
// Validate contrast ratios in design system
```
---
## Security Best Practices
### Secure Storage
```typescript
import * as SecureStore from 'expo-secure-store';
// ✅ GOOD: Encrypted storage for sensitive data
await SecureStore.setItemAsync('auth_token', token);
// ❌ BAD: AsyncStorage for sensitive data (unencrypted)
await AsyncStorage.setItem('auth_token', token);
```
### Certificate Pinning
```typescript
// Prevent man-in-the-middle attacks
import { configureCertificatePinning } from 'react-native-cert-pinner';
await configureCertificatePinning([
{
hostname: 'api.maternalapp.com',
certificates: [
'sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=',
],
},
]);
```
### Jailbreak/Root Detection
```typescript
import JailMonkey from 'jail-monkey';
if (JailMonkey.isJailBroken()) {
Alert.alert(
'Security Warning',
'This app may not function properly on jailbroken devices'
);
}
```
---
## Migration Path from Web to Mobile
### Phase 1: Extract Shared Logic
```typescript
// 1. Move business logic to shared package
// packages/shared/src/services/
export class ActivityService { ... }
export class AIService { ... }
// 2. Update web app to use shared package
import { ActivityService } from '@maternal/shared';
```
### Phase 2: Build Mobile Shell
```typescript
// 1. Create React Native app with Expo
npx create-expo-app maternal-mobile
// 2. Set up navigation structure
// 3. Integrate shared services
// 4. Build basic UI with React Native Paper
```
### Phase 3: Implement Mobile-Specific Features
```typescript
// 1. Offline mode with SQLite
// 2. Push notifications
// 3. Biometric auth
// 4. Voice input
// 5. Camera integration
```
### Phase 4: Testing & Optimization
```typescript
// 1. Unit tests
// 2. Component tests
// 3. E2E tests with Detox
// 4. Performance profiling
// 5. Accessibility audit
```
### Phase 5: Beta Testing & Launch
```typescript
// 1. TestFlight (iOS)
// 2. Google Play Internal Testing
// 3. Gather feedback
// 4. Iterate based on metrics
// 5. Production launch
```
---
## Conclusion
This guide provides a comprehensive roadmap for implementing native mobile apps. Key takeaways:
1. **Code Reusability**: Share business logic between web and mobile
2. **Offline-First**: Essential for mobile UX
3. **Native Features**: Leverage platform-specific capabilities
4. **Performance**: Optimize for mobile constraints
5. **Testing**: Comprehensive strategy for quality
6. **Security**: Protect user data on mobile devices
7. **Analytics**: Track usage and iterate
The current web implementation already follows many mobile-friendly patterns, making the transition to React Native straightforward when the time comes.