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
-
Archive Build
- Select "Any iOS Device" or actual device
- Product → Archive
- Ensure Release configuration
-
Upload to App Store Connect
- Open Organizer (Window → Organizer)
- Select archive → Distribute App
- Choose "App Store Connect"
- Upload
-
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)
-
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:
- Use semantic versioning consistently
- Test thoroughly on TestFlight/Internal Testing before production
- Complete all store listing requirements
- Declare all data collection and permissions
- Automate builds with CI/CD
- Monitor post-release metrics
- Maintain rollback capability
| Platform | Beta Distribution | Production | Automation |
|---|---|---|---|
| iOS | TestFlight | App Store Connect | Fastlane |
| Android | Internal Testing | Play Console | Fastlane |
| Flutter | Platform-specific | Platform-specific | CI/CD + Fastlane |