MyAIDevelopment · Product · Operations notes

Mobile Store Distribution: App Store and Google Play

The web reaches every user as soon as you deploy, but a mobile app passes two gates: store review and users updating. That is why schedules, policies and gradual releases must be designed in advance.

The Whole Flow

flowchart LR
    B[Build / Sign] --> UP[Upload]
    UP --> T[Test Track / TestFlight]
    T --> R{Store Review}
    R -->|approved| P[Gradual release]
    R -->|rejected| F[Fix / Resubmit]
    F --> UP
    P --> M[Watch crashes / metrics]
    M -->|anomaly| H[Halt / Hotfix]
    M -->|healthy| A[Release to 100%]

Accounts and Cost Model

Item Apple Google
Developer account Apple Developer Program annual membership Play Console one-time registration fee
Review tool App Store Connect Play Console
Pre-release testing TestFlight Internal / Closed / Open testing tracks

Amounts vary by region and over time, so check them on the enrollment screen.

Apple: Review and Testing

Apple: Requirements Verified for 2026

Effective Requirement
2024-05-01 If you use listed APIs (required reason APIs), declare approved reasons in the privacy manifest
2026-01-31 Answer the new age rating questions so update submissions are not interrupted
2026-04-28 Builds must use Xcode 26 or later with a 26 SDK (e.g. iOS 26) to upload
2026-09-09 iOS · iPadOS apps must target iOS 13 or later to upload
2026-09 (in force) Submitting new apps and updates, and notarization for alternative distribution, require answers to the social media capability questions in the age rating questionnaire. Apps that declare the capability go into the Time Allowance "Social Media" category with a minimum age rating of 13+. Apps that disable it for users under 13 must check age ranges with the Declared Age Range API
2027-04 (announced) iOS · iPadOS apps must be built with the iOS 27 · iPadOS 27 SDK or later (Xcode 27) to upload. tvOS, visionOS and watchOS need their 27 SDKs too

Google Play: Tracks and New-Account Conditions

Track Purpose
Internal testing Up to 100 testers, fast internal QA
Closed testing Chosen tester groups, multiple tracks allowed
Open testing Anyone can join; the test version is visible on the Play Store
Production General release, staged rollout available

Personal developer accounts created on or after 2023-11-13 must run a closed test with at least 12 testers opted in for 14 consecutive days before applying for production access. Meeting the conditions does not guarantee approval. Organization accounts and older personal accounts are not affected.

Google Play: Requirements Verified for 2026

Effective Requirement
2026-08-31 New apps and updates must target Android 16 (API 36) or higher. Wear OS · Automotive: API 35; TV · XR: API 34
2026-08-31 Existing apps must target API 35 or higher to stay available to new users on newer Android versions
2026-11-01 Latest date available when requesting an extension to the above
2026-09-30 Android developer verification starts in Brazil, Indonesia, Singapore and Thailand (certified devices, participating stores)
2025-11-01 (in force) New apps and updates targeting Android 15+ must support 16 KB page sizes on 64-bit devices. This affects apps with native (C/C++) code or native libraries; apps using only Kotlin or Java are already compatible
2027-02-01 Final deadline for updates to existing apps after the extension period: updates without 16 KB page size support cannot be released
android {
    defaultConfig {
        targetSdk = 36
    }
}

Signing and Key Management

For a mobile app, the signing key is the app's identity. Lose the key or miss an expiry and you cannot ship updates, even with perfectly good code.

Android: Play App Signing

Key Held by Purpose If lost
App signing key Google Signs the APKs installed on user devices. Never changes during the app's lifetime Google holds it, so the developer cannot lose it
Upload key Developer Signs the AAB before uploading to Play Console Request an upload key reset in Play Console; only the account owner can request it. You can keep updating the same app

Apple: Certificates and Provisioning Profiles

Where Keys Live in CI

Approach Description
CI secret Store the keystore · p12 as Base64 in a secret and restore it to a temp file in the job. Most common, but rotation and auditing are manual
fastlane match Encrypts certificates and profiles in a separate store (Git and others) shared by the team and CI. Use readonly mode on CI so it never creates new certificates
EAS (Expo) Keeps the keystore and distribution certificate encrypted on EAS servers and uses them at build time
Xcode Cloud Apple's CI/CD. Cloud signing hands certificate and profile management to Apple

Release automation tools: fastlane (upload_to_play_store / supply, actions for App Store and TestFlight upload), EAS Submit, Xcode Cloud (distribution to TestFlight and App Store Connect), and the Google Play Developer API (upload AABs in an edit, set track and rollout).

Costly Failures

Mistake Result
Losing a self-managed Android signing key No more updates to the existing app
Upload key or keystore password in the repository or build files Treat it as leaked and reset the upload key. Keep passwords outside build files (environment variables, a separate file)
Discovering an expired distribution certificate on launch day Uploads fail and the launch slips. Put expiry dates on a calendar and in alerts
Expired in-house certificate Every internal app stops launching at once
Keys only on one person's laptop A resignation or lost laptop halts releases

CI Upload Example (Android → Internal Track)

jobs:
  android-release:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@<commit-sha>
      - run: echo "$UPLOAD_KEYSTORE_B64" | base64 -d > "$RUNNER_TEMP/upload.jks"
        env:
          UPLOAD_KEYSTORE_B64: ${{ secrets.UPLOAD_KEYSTORE_B64 }}
      - run: ./gradlew bundleRelease
        env:
          UPLOAD_KEYSTORE_PATH: ${{ runner.temp }}/upload.jks
          UPLOAD_KEYSTORE_PASSWORD: ${{ secrets.UPLOAD_KEYSTORE_PASSWORD }}
      - run: >-
          fastlane supply
          --aab app/build/outputs/bundle/release/app-release.aab
          --package_name com.example.app
          --track internal
          --json_key_data "$PLAY_JSON"
        env:
          PLAY_JSON: ${{ secrets.PLAY_SERVICE_ACCOUNT_JSON }}

For iOS → TestFlight, write a fastlane lane. It authenticates with an App Store Connect API key, and match runs readonly so it only downloads existing certificates and profiles.

lane :beta do
  key = app_store_connect_api_key(key_id: ENV["ASC_KEY_ID"], issuer_id: ENV["ASC_ISSUER_ID"], key_content: ENV["ASC_KEY_P8"])
  match(type: "appstore", readonly: true, api_key: key)
  build_app(scheme: "App")
  upload_to_testflight(api_key: key)
end

Running a Staged Rollout

Bad Example / Good Example

Bad:  First submission the day before launch. A login-only app without a demo account is rejected; launch slips.
Good: Start TestFlight and a closed test two weeks early, prepare a demo account for review,
      and use phased release plus server feature flags to limit exposure if problems appear.

Pre-Submission Checklist

References