Files
maternal-app/docs/maternal-app-mobile-deployment.md
2025-10-01 19:01:52 +00:00

15 KiB

Mobile Build & Deployment Guide - Maternal Organization App

Build Environment Setup

Prerequisites

# Required tools
node >= 18.0.0
npm >= 9.0.0
react-native-cli >= 2.0.1
expo-cli >= 6.0.0
cocoapods >= 1.12.0 (iOS)
java 11 (Android)
android-studio (Android)
xcode >= 14.0 (iOS)

Project Initialization

# Create project with Expo
npx create-expo-app maternal-app --template

# Install core dependencies
cd maternal-app
npm install react-native-reanimated react-native-gesture-handler
npm install react-native-safe-area-context react-native-screens
npm install @react-navigation/native @react-navigation/bottom-tabs

iOS Configuration

Bundle Identifier & Provisioning

<!-- ios/MaternalApp/Info.plist -->
<key>CFBundleIdentifier</key>
<string>com.maternalapp.ios</string>
<key>CFBundleDisplayName</key>
<string>Maternal</string>
<key>CFBundleShortVersionString</key>
<string>1.0.0</string>
<key>CFBundleVersion</key>
<string>1</string>

App Capabilities

<!-- ios/MaternalApp/MaternalApp.entitlements -->
<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0">
<dict>
    <key>aps-environment</key>
    <string>production</string>
    <key>com.apple.developer.healthkit</key>
    <true/>
    <key>com.apple.developer.healthkit.background-delivery</key>
    <true/>
    <key>com.apple.security.application-groups</key>
    <array>
        <string>group.com.maternalapp.shared</string>
    </array>
</dict>
</plist>

Permissions

<!-- ios/MaternalApp/Info.plist -->
<key>NSCameraUsageDescription</key>
<string>Take photos of your child for memories and milestone tracking</string>
<key>NSMicrophoneUsageDescription</key>
<string>Enable voice input for hands-free activity logging</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Select photos for your child's profile and milestones</string>
<key>NSSpeechRecognitionUsageDescription</key>
<string>Convert your voice to text for quick logging</string>
<key>NSHealthShareUsageDescription</key>
<string>Read health data to track your child's growth</string>
<key>NSHealthUpdateUsageDescription</key>
<string>Save growth measurements to Health app</string>

Code Signing Configuration

# ios/fastlane/Fastfile
platform :ios do
  desc "Build and deploy to TestFlight"
  lane :beta do
    increment_build_number
    
    match(
      type: "appstore",
      app_identifier: "com.maternalapp.ios",
      git_url: "git@github.com:maternal-app/certificates.git"
    )
    
    gym(
      scheme: "MaternalApp",
      configuration: "Release",
      export_method: "app-store",
      output_directory: "./build",
      output_name: "MaternalApp.ipa"
    )
    
    pilot(
      ipa: "./build/MaternalApp.ipa",
      skip_waiting_for_build_processing: true,
      changelog: "Bug fixes and performance improvements"
    )
  end
end

Android Configuration

Package Name & Versioning

// android/app/build.gradle
android {
    compileSdkVersion 34
    buildToolsVersion "34.0.0"
    
    defaultConfig {
        applicationId "com.maternalapp.android"
        minSdkVersion 23  // Android 6.0
        targetSdkVersion 34
        versionCode 1
        versionName "1.0.0"
        
        multiDexEnabled true
    }
    
    signingConfigs {
        release {
            storeFile file(MATERNAL_RELEASE_STORE_FILE)
            storePassword MATERNAL_RELEASE_STORE_PASSWORD
            keyAlias MATERNAL_RELEASE_KEY_ALIAS
            keyPassword MATERNAL_RELEASE_KEY_PASSWORD
        }
    }
    
    buildTypes {
        release {
            signingConfig signingConfigs.release
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

Permissions

<!-- android/app/src/main/AndroidManifest.xml -->
<uses-permission android:name="android.permission.INTERNET" />
<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.VIBRATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

<!-- Google Play Services -->
<uses-permission android:name="com.google.android.gms.permission.AD_ID" tools:node="remove" />

ProGuard Rules

# android/app/proguard-rules.pro
-keep class com.maternalapp.** { *; }
-keep class com.facebook.react.** { *; }
-keep class com.swmansion.** { *; }

# React Native
-keep,allowobfuscation @interface com.facebook.proguard.annotations.DoNotStrip
-keep,allowobfuscation @interface com.facebook.proguard.annotations.KeepGettersAndSetters

# Firebase
-keep class com.google.firebase.** { *; }
-keep class com.google.android.gms.** { *; }

Environment-Specific Builds

Build Configurations

// app.config.js
export default ({ config }) => {
  const buildType = process.env.BUILD_TYPE || 'development';
  
  const configs = {
    development: {
      name: 'Maternal (Dev)',
      bundleIdentifier: 'com.maternalapp.dev',
      apiUrl: 'https://dev-api.maternalapp.com',
      icon: './assets/icon-dev.png',
    },
    staging: {
      name: 'Maternal (Staging)',
      bundleIdentifier: 'com.maternalapp.staging',
      apiUrl: 'https://staging-api.maternalapp.com',
      icon: './assets/icon-staging.png',
    },
    production: {
      name: 'Maternal',
      bundleIdentifier: 'com.maternalapp.ios',
      apiUrl: 'https://api.maternalapp.com',
      icon: './assets/icon.png',
    },
  };
  
  return {
    ...config,
    ...configs[buildType],
    ios: {
      ...config.ios,
      bundleIdentifier: configs[buildType].bundleIdentifier,
      buildNumber: '1',
    },
    android: {
      ...config.android,
      package: configs[buildType].bundleIdentifier.replace('ios', 'android'),
      versionCode: 1,
    },
  };
};

Environment Variables

# .env.production
API_URL=https://api.maternalapp.com
SENTRY_DSN=https://prod-sentry.maternalapp.com
ANALYTICS_ENABLED=true
CRASH_REPORTING=true

# .env.staging
API_URL=https://staging-api.maternalapp.com
SENTRY_DSN=https://staging-sentry.maternalapp.com
ANALYTICS_ENABLED=true
CRASH_REPORTING=true

# .env.development
API_URL=http://localhost:3000
SENTRY_DSN=
ANALYTICS_ENABLED=false
CRASH_REPORTING=false

CI/CD Pipeline

GitHub Actions Workflow

# .github/workflows/mobile-deploy.yml
name: Mobile Build & Deploy

on:
  push:
    branches: [main, staging]
    tags: ['v*']
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-node@v3
        with:
          node-version: '18'
      - run: npm ci
      - run: npm test
      - run: npm run lint

  build-ios:
    needs: test
    runs-on: macos-latest
    if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup Node
        uses: actions/setup-node@v3
        with:
          node-version: '18'
          
      - name: Install dependencies
        run: |
          npm ci
          cd ios && pod install
          
      - name: Setup certificates
        env:
          CERTIFICATE_BASE64: ${{ secrets.IOS_CERTIFICATE_BASE64 }}
          PROVISION_PROFILE_BASE64: ${{ secrets.IOS_PROVISION_PROFILE_BASE64 }}
          KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
        run: |
          # Create keychain
          security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
          security default-keychain -s build.keychain
          security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
          
          # Import certificate
          echo "$CERTIFICATE_BASE64" | base64 --decode > certificate.p12
          security import certificate.p12 -k build.keychain -P "${{ secrets.CERTIFICATE_PASSWORD }}" -T /usr/bin/codesign
          
          # Import provisioning profile
          echo "$PROVISION_PROFILE_BASE64" | base64 --decode > profile.mobileprovision
          mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
          cp profile.mobileprovision ~/Library/MobileDevice/Provisioning\ Profiles/
          
      - name: Build IPA
        run: |
          cd ios
          xcodebuild -workspace MaternalApp.xcworkspace \
            -scheme MaternalApp \
            -configuration Release \
            -archivePath $PWD/build/MaternalApp.xcarchive \
            archive
            
          xcodebuild -exportArchive \
            -archivePath $PWD/build/MaternalApp.xcarchive \
            -exportPath $PWD/build \
            -exportOptionsPlist ExportOptions.plist
            
      - name: Upload to TestFlight
        env:
          APP_STORE_CONNECT_API_KEY: ${{ secrets.APP_STORE_CONNECT_API_KEY }}
        run: |
          xcrun altool --upload-app \
            --type ios \
            --file ios/build/MaternalApp.ipa \
            --apiKey "${{ secrets.API_KEY_ID }}" \
            --apiIssuer "${{ secrets.API_ISSUER_ID }}"

  build-android:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v')
    steps:
      - uses: actions/checkout@v3
      
      - name: Setup Node
        uses: actions/setup-node@v3
        with:
          node-version: '18'
          
      - name: Setup Java
        uses: actions/setup-java@v3
        with:
          distribution: 'temurin'
          java-version: '11'
          
      - name: Install dependencies
        run: npm ci
        
      - name: Setup keystore
        env:
          KEYSTORE_BASE64: ${{ secrets.ANDROID_KEYSTORE_BASE64 }}
        run: |
          echo "$KEYSTORE_BASE64" | base64 --decode > android/app/release.keystore
          echo "MATERNAL_RELEASE_STORE_FILE=release.keystore" >> android/gradle.properties
          echo "MATERNAL_RELEASE_KEY_ALIAS=${{ secrets.ANDROID_KEY_ALIAS }}" >> android/gradle.properties
          echo "MATERNAL_RELEASE_STORE_PASSWORD=${{ secrets.ANDROID_STORE_PASSWORD }}" >> android/gradle.properties
          echo "MATERNAL_RELEASE_KEY_PASSWORD=${{ secrets.ANDROID_KEY_PASSWORD }}" >> android/gradle.properties
          
      - name: Build APK
        run: |
          cd android
          ./gradlew assembleRelease
          
      - name: Build AAB
        run: |
          cd android
          ./gradlew bundleRelease
          
      - name: Upload to Play Store
        uses: r0adkll/upload-google-play@v1
        with:
          serviceAccountJsonPlainText: ${{ secrets.PLAY_SERVICE_ACCOUNT_JSON }}
          packageName: com.maternalapp.android
          releaseFiles: android/app/build/outputs/bundle/release/app-release.aab
          track: internal
          status: draft

TestFlight Configuration

App Store Connect Setup

// ios/fastlane/metadata/en-US/description.txt
Maternal is your AI-powered parenting companion, designed to reduce mental load and bring confidence to your parenting journey.

Key Features:
 Smart activity tracking with voice input
 AI assistant available 24/7 for parenting questions
 Real-time family synchronization
 Sleep predictions based on your baby's patterns
 Growth tracking with WHO percentiles

// ios/fastlane/metadata/en-US/keywords.txt
parenting,baby tracker,sleep tracking,feeding log,AI assistant,family app,childcare

Beta Testing Groups

# TestFlight Groups
internal_testing:
  name: "Internal Team"
  members: 10
  builds: all
  
beta_families:
  name: "Beta Families"
  members: 50
  builds: stable
  feedback: enabled
  
early_access:
  name: "Early Access"
  members: 500
  builds: release_candidate

Google Play Console Configuration

Store Listing

# Play Console Setup
app_details:
  title: "Maternal - AI Parenting Assistant"
  short_description: "Smart parenting companion with AI support"
  full_description: |
    Complete description...
  
  category: "Parenting"
  content_rating: "Everyone"
  
graphics:
  icon: 512x512px
  feature_graphic: 1024x500px
  screenshots:
    phone: [6 images minimum]
    tablet: [optional]

Release Tracks

internal_testing:
  testers: "internal-testers@maternalapp.com"
  release_frequency: "daily"
  
closed_testing:
  testers: 100
  release_frequency: "weekly"
  
open_testing:
  countries: ["US", "CA", "GB", "AU"]
  release_frequency: "bi-weekly"
  
production:
  rollout_percentage: 10  # Start with 10%
  staged_rollout: true

Over-the-Air Updates

CodePush Setup

# Install CodePush
npm install react-native-code-push

# iOS setup
cd ios && pod install

# Register app with CodePush
code-push app add Maternal-iOS ios react-native
code-push app add Maternal-Android android react-native

Update Configuration

// App.js
import CodePush from 'react-native-code-push';

const codePushOptions = {
  checkFrequency: CodePush.CheckFrequency.ON_APP_RESUME,
  installMode: CodePush.InstallMode.ON_NEXT_RESTART,
  mandatoryInstallMode: CodePush.InstallMode.IMMEDIATE,
  updateDialog: {
    title: 'Update Available',
    mandatoryUpdateMessage: 'An important update is available.',
    optionalUpdateMessage: 'An update is available. Would you like to install it?',
  },
};

export default CodePush(codePushOptions)(App);

Deployment Commands

# Deploy update to staging
code-push release-react Maternal-iOS ios -d Staging
code-push release-react Maternal-Android android -d Staging

# Promote to production
code-push promote Maternal-iOS Staging Production -r 10%
code-push promote Maternal-Android Staging Production -r 10%

Performance Monitoring

Build Size Optimization

// metro.config.js
module.exports = {
  transformer: {
    minifierConfig: {
      keep_fnames: true,
      mangle: {
        keep_fnames: true,
      },
    },
  },
};

Bundle Analysis

# Analyze bundle size
npx react-native-bundle-visualizer

# iOS specific
npx react-native bundle --platform ios --dev false --entry-file index.js --bundle-output ios/main.jsbundle --assets-dest ios

# Android specific
cd android && ./gradlew bundleRelease --scan

Pre-Launch Checklist

iOS Submission

  • TestFlight build approved
  • App Store screenshots (6.5", 5.5")
  • App preview video (optional)
  • Privacy policy URL
  • Support URL
  • Marketing URL
  • Age rating questionnaire
  • Export compliance
  • App Review notes

Android Submission

  • Signed AAB uploaded
  • Store listing complete
  • Content rating questionnaire
  • Target audience declaration
  • Data safety form
  • Privacy policy URL
  • App category selected
  • Closed testing feedback addressed

General Requirements

  • COPPA compliance verified
  • GDPR compliance documented
  • Terms of service updated
  • Support system ready
  • Analytics tracking verified
  • Crash reporting active
  • Performance benchmarks met
  • Accessibility tested