Skip to main content

Versioning & Release Management

Semantic Versioning

Use semantic versioning: MAJOR.MINOR.PATCH

  • MAJOR: Breaking changes, incompatible API changes
  • MINOR: New features, backward-compatible
  • PATCH: Bug fixes, backward-compatible

iOS Versioning

MARKETING_VERSION = 1.4.0        // User-facing version
CURRENT_PROJECT_VERSION = 10400 // Build number (increments every build)

Set in Xcode project settings or via xcconfig file:

// Config/Version.xcconfig
MARKETING_VERSION = 1.4.0
CURRENT_PROJECT_VERSION = 10400

Android Versioning

// app/build.gradle.kts
android {
defaultConfig {
versionName = "1.4.0" // User-facing version
versionCode = 10400 // Integer, must increment
}
}

versionCode calculation: MAJOR * 10000 + MINOR * 100 + PATCH

  • 1.4.0 → 10400
  • 1.4.1 → 10401

Use the same numeric build value across platforms (iOS build number, Android versionCode, Flutter build number) to keep releases aligned.

Flutter Versioning

# pubspec.yaml
version: 1.4.0+10400
# ↑ ↑
# version build number

TestFlight (iOS)

Process

  1. Archive Build

    • Select "Any iOS Device" or actual device
    • Product → Archive
    • Ensure Release configuration
  2. Upload to App Store Connect

    • Open Organizer (Window → Organizer)
    • Select archive → Distribute App
    • Choose "App Store Connect"
    • Upload
  3. Configure TestFlight

    • Log into App Store Connect
    • Select app → TestFlight tab
    • Add internal testers (automatic, up to 100)
    • Add external testers (requires review for new builds)
  4. Add Test Information

    • What to test
    • Known issues
    • Contact information

Pre-Flight Checklist

  • Release configuration enabled
  • Correct bundle identifier
  • Certificates and provisioning profiles valid
  • Info.plist privacy descriptions complete
  • No debug logging or test endpoints
  • App launches without network
  • Core flows functional (login, navigation)
  • Analytics and crash reporting working

Testing on TestFlight

// Detect TestFlight environment
var isTestFlight: Bool {
guard let path = Bundle.main.appStoreReceiptURL?.path else {
return false
}
return path.contains("sandboxReceipt")
}

// Enable debug features in TestFlight
#if DEBUG || TESTFLIGHT
enableDebugMenu()
#endif

App Store Submission (iOS)

Technical Checklist

  • App built with Release configuration
  • All architectures included (arm64)
  • Bitcode setting verified (Bitcode is deprecated; most projects should keep it disabled)
  • App Thinning enabled
  • No debug symbols in release
  • Privacy manifest included (if required)
  • Third-party SDK disclosures complete
  • Export compliance answered

Product Checklist

  • App name (max 30 characters)
  • Subtitle (max 30 characters)
  • App description
  • Keywords (max 100 characters, comma-separated)
  • Support URL
  • Marketing URL (optional)
  • Privacy policy URL (required)
  • Screenshots (all required sizes)
    • 6.7" (iPhone 14 Pro Max)
    • 6.5" (iPhone 11 Pro Max)
    • 5.5" (iPhone 8 Plus)
    • iPad Pro (12.9" 3rd gen)
    • iPad Pro (12.9" 2nd gen)
  • App icon (1024x1024, no transparency)
  • Age rating completed
  • Category selected
  • In-app purchases configured (if applicable)

Privacy & Compliance

  • Privacy Nutrition Labels: Declare all data collection
    • Data used to track you
    • Data linked to you
    • Data not linked to you
  • App Tracking Transparency: Request permission if tracking
  • Export Compliance: Encryption usage declared
  • Content Rights: Ensure all content is licensed

App Review Guidelines

Common rejection reasons:

  • Incomplete information
  • Crashes on launch
  • Broken links
  • Missing privacy policy
  • Improper use of permissions
  • Placeholder content
  • Violating design guidelines

Play Store Submission (Android)

Technical Checklist

  • Release build variant
  • Signed with production keystore
  • ProGuard/R8 rules validated
  • Target SDK = current stable Android API used by the project
  • Compile SDK = current stable Android API used by the project
  • Min SDK appropriate for target users
  • No android:debuggable="true"
  • No test or debug code
  • App Bundle (AAB) format (preferred)
// app/build.gradle.kts
android {
compileSdk = 34

defaultConfig {
minSdk = 24
targetSdk = 34
}

buildTypes {
release {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}

signingConfigs {
create("release") {
storeFile = file(System.getenv("KEYSTORE_PATH"))
storePassword = System.getenv("KEYSTORE_PASSWORD")
keyAlias = System.getenv("KEY_ALIAS")
keyPassword = System.getenv("KEY_PASSWORD")
}
}
}

Product Checklist

  • App name (max 50 characters)
  • Short description (max 80 characters)
  • Full description (max 4000 characters)
  • Screenshots (minimum 2, maximum 8)
    • Phone: 16:9 or 9:16 ratio
    • 7" tablet: 16:9 or 9:16 ratio
    • 10" tablet: 16:9 or 9:16 ratio
  • Feature graphic (1024w x 500h)
  • App icon (512x512, 32-bit PNG)
  • Content rating questionnaire completed
  • App category selected
  • Contact email
  • Privacy policy URL (required for apps accessing personal data)

Data Safety Form

Declare all data collection and sharing:

  • Location data
  • Personal information (name, email, etc.)
  • Financial information
  • Health & fitness data
  • Messages & communication
  • Photos & videos
  • Audio files
  • Files & docs
  • App activity (interactions, search history)
  • Device or other identifiers

For each data type:

  • Is it collected or shared?
  • Is collection required or optional?
  • Purpose of collection
  • Is it encrypted in transit?
  • Can users request deletion?

Pre-Launch Report

Google Play automatically tests your app on real devices:

  • Crashes
  • Performance issues
  • Accessibility issues
  • Security vulnerabilities

Review and fix issues before production release.

Flutter Release

iOS Release

# Build for release
flutter build ios --release

# Or build archive
flutter build ipa

# Generated at: build/ios/archive/Runner.xcarchive
# Upload via Xcode Organizer or Transporter app

Android Release

# Build AAB (recommended)
flutter build appbundle --release

# Or build APK
flutter build apk --release --split-per-abi

# Generated at:
# build/app/outputs/bundle/release/app-release.aab
# build/app/outputs/apk/release/app-armeabi-v7a-release.apk

Build Flavors

// android/app/build.gradle.kts
android {
flavorDimensions += "environment"

productFlavors {
create("dev") {
dimension = "environment"
applicationIdSuffix = ".dev"
versionNameSuffix = "-dev"
}

create("staging") {
dimension = "environment"
applicationIdSuffix = ".staging"
versionNameSuffix = "-staging"
}

create("prod") {
dimension = "environment"
}
}
}
# Build specific flavor
flutter build apk --flavor prod --release
flutter build ios --flavor staging --release

CI/CD Automation

Fastlane (iOS & Android)

# fastlane/Fastfile
platform :ios do
desc "Build and upload to TestFlight"
lane :beta do
increment_build_number
build_app(scheme: "MyApp")
upload_to_testflight(
skip_waiting_for_build_processing: true
)
end

desc "Deploy to App Store"
lane :release do
build_app(scheme: "MyApp")
upload_to_app_store(
submit_for_review: true,
automatic_release: false
)
end
end

platform :android do
desc "Deploy to Play Store Beta"
lane :beta do
gradle(
task: "bundle",
build_type: "Release"
)
upload_to_play_store(
track: "internal",
aab: "app/build/outputs/bundle/release/app-release.aab"
)
end

desc "Deploy to Play Store Production"
lane :release do
gradle(
task: "bundle",
build_type: "Release"
)
upload_to_play_store(
track: "production",
aab: "app/build/outputs/bundle/release/app-release.aab"
)
end
end

GitHub Actions Example

# .github/workflows/release-ios.yml
name: iOS Release

on:
push:
tags:
- "v*"

jobs:
build:
runs-on: macos-latest
steps:
- uses: actions/checkout@v3

- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: "3.16.0"

- name: Install dependencies
run: flutter pub get

- name: Build iOS
run: flutter build ios --release --no-codesign

- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: "3.0"

- name: Install Fastlane
run: gem install fastlane

- name: Deploy to TestFlight
env:
FASTLANE_PASSWORD: ${{ secrets.FASTLANE_PASSWORD }}
MATCH_PASSWORD: ${{ secrets.MATCH_PASSWORD }}
run: fastlane ios beta

Release Checklist Summary

Pre-Release

  • All features tested
  • Regression testing complete
  • Performance acceptable
  • Crash-free rate > 99%
  • No blocking bugs
  • Release notes prepared
  • Screenshots updated
  • Privacy policy updated

Release Day

  • Version number incremented
  • Build submitted to store
  • Release notes uploaded
  • Support team notified
  • Monitoring alerts configured
  • Rollback plan ready

Post-Release

  • Monitor crash reports
  • Check analytics for anomalies
  • Review user feedback
  • Respond to reviews
  • Plan hotfix if needed

Summary

Release Process Best Practices:

  1. Use semantic versioning consistently
  2. Test thoroughly on TestFlight/Internal Testing before production
  3. Complete all store listing requirements
  4. Declare all data collection and permissions
  5. Automate builds with CI/CD
  6. Monitor post-release metrics
  7. Maintain rollback capability
PlatformBeta DistributionProductionAutomation
iOSTestFlightApp Store ConnectFastlane
AndroidInternal TestingPlay ConsoleFastlane
FlutterPlatform-specificPlatform-specificCI/CD + Fastlane