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

22 KiB

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

{
  "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

// ✅ 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

// ❌ 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

// 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

// 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

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

// 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

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

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

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

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

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

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)

// 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

// 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)

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)

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)

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

- ✅ 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

<!-- 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

<key>UIBackgroundModes</key>
<array>
  <string>remote-notification</string>
  <string>fetch</string>
</array>

Android Specific

1. Permissions

<!-- 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

# 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

# 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)

# 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)

cd android
./gradlew bundleRelease

# Output: android/app/build/outputs/bundle/release/app-release.aab

2. Internal Testing Track

# Upload to Google Play Console
# Use Fastlane or manual upload

Over-the-Air Updates (CodePush)

Setup for rapid iteration

# 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

# 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)

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

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

// 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

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

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

// 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

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

// 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

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

// 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

// 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

// 1. Offline mode with SQLite
// 2. Push notifications
// 3. Biometric auth
// 4. Voice input
// 5. Camera integration

Phase 4: Testing & Optimization

// 1. Unit tests
// 2. Component tests
// 3. E2E tests with Detox
// 4. Performance profiling
// 5. Accessibility audit

Phase 5: Beta Testing & Launch

// 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.