App Store Distribution & Release Strategy
TL;DR
TestFlight (iOS): Beta testing for up to 10,000 testers. 90 days max. Catches crashes before App Store release.
Play Console (Android): Internal testing, closed/open beta tracks. Gradual rollout (1% → 5% → 100%). Test smaller % first.
Gradual Rollout: Release to 1% of users, monitor crashes/feedback, expand if healthy. Catch issues affecting millions before they happen.
Versioning: Use SemVer (major.minor.patch). Communicate breaking changes. Support min OS versions.
Policies: App Store review (1-3 days), strict policies (no web apps, subscription disclosure). Plan review time into timeline.
Learning Objectives
You will be able to:
- Set up beta testing (TestFlight, Play Console).
- Plan gradual rollouts and monitor health metrics.
- Understand app store policies and approval process.
- Implement over-the-air update strategies (CodePush, Firebase).
- Manage versioning and backward compatibility.
Motivating Scenario
You release v2.0 with breaking changes. All users auto-update. Within 1 hour, 10% of users hit a crash. Users flood support, leave bad reviews. App rating plummets. Recovery takes weeks.
With proper release strategy:
- Beta phase: 1000 TestFlight testers catch crash
- Gradual rollout: Release to 1%, monitor. Crash detected after 100 users, not 1 million
- Quick rollback: Within 2 hours, rollback to v1.9
- Loss: 100 users disappointed vs. 1M
Beta Testing
iOS: TestFlight
Test before App Store release:
// 1. Build signed app for TestFlight
// In Xcode:
// - Product → Archive
// - Distribute App → TestFlight & App Store
// - Select "TestFlight Only"
// - Upload
// 2. Configure tester groups in App Store Connect
// - TestFlight tab
// - External Testers: add email addresses
// - Send invitations
// 3. Testers download app from TestFlight app
// - Fixed for 90 days
// - Can test multiple builds
// - Feedback via built-in form
// 4. Monitor crashes
// - Crashes tab shows stack traces
// - Session logs available
// - Real-world device diagnostics
Android: Play Console
Multi-track beta testing:
android {
// Build for different tracks
flavorDimensions "track"
productFlavors {
// Production: live on Play Store
production {
dimension "track"
applicationIdSuffix ""
}
// Beta: only for beta testers
beta {
dimension "track"
applicationIdSuffix ".beta"
versionNameSuffix "-beta"
}
// Internal: internal team only
internal {
dimension "track"
applicationIdSuffix ".internal"
versionNameSuffix "-internal"
}
}
}
// In Play Console:
// - Internal testing: instant, small team
// - Closed beta: selected testers (max 500 for closed, unlimited for open)
// - Staged rollout: 1% → 5% → 10% → 100%
Gradual Rollout Strategy
Release in stages, monitor metrics:
Stage 1: 1% (10,000 users if 1M base)
- Monitor crash rate (target: <0.1%)
- Monitor ANR rate (target: <0.5%)
- Session length stability
- Error logs
Stage 2: 5% (expand if healthy)
- Larger sample size for confidence
- Check for regional issues (network, locale)
- User feedback
Stage 3: 25-50% (expand further)
- High confidence in stability
- Performance metrics across device types
Stage 4: 100% (full release)
- All users receive update
Metrics to monitor:
// Monitor these metrics during rollout
data class HealthMetrics(
val crashRate: Double, // Crashes per session
val anrRate: Double, // Application Not Responding
val sessionLength: Double, // Average session duration
val errorRate: Double, // API errors, unhandled exceptions
val performanceImpact: Double, // Slow downs vs previous version
)
fun shouldExpandRollout(metrics: HealthMetrics): Boolean {
return metrics.crashRate < 0.001 // <0.1%
&& metrics.anrRate < 0.005 // <0.5%
&& metrics.sessionLength > 0.9 // >=90% of previous
&& metrics.errorRate < 0.02 // <2%
}
fun shouldRollback(metrics: HealthMetrics): Boolean {
return metrics.crashRate > 0.01 // >1%
|| metrics.sessionLength < 0.7 // <70% of previous
}
Versioning & Compatibility
Semantic Versioning
major.minor.patch (e.g., 2.5.3)
major: breaking changes (API changes, min OS bump)
minor: new features (backward compatible)
patch: bug fixes
Example timeline:
v2.0.0: Requires iOS 14+ (breaking, needs upgrade)
v2.1.0: New search feature (backward compatible)
v2.1.1: Fix crash bug (patch)
Communicate Changes
# Version 2.0.0 Release Notes
## New Features
- Dark mode support
- Offline-first editing
## Breaking Changes
- Requires iOS 14+ (dropped iOS 13 support)
- Old login method removed (migrate to OAuth)
## Migration Guide
[Link to migration docs]
## Bug Fixes
- Fixed crash on startup
Support Min OS Version
Know your user base:
// Check version at runtime
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
// API level < 23 (Android 6)
// Disable features not available
disableNativeLib()
useBackupImpl()
} else {
useNativeLib()
}
// Track users by OS version
analytics.trackEvent("AppOpened", {
"osVersion" = Build.VERSION.SDK_INT,
})
// Decision: How many OS versions to support?
// Common: current + 2 previous (3 versions)
// Aggressive: current only
// Conservative: current + 3-4 previous
Over-the-Air Updates
Update without app store approval (JavaScript/assets only):
CodePush (React Native)
const App = () => {
return <MainApp />;
};
export default CodePush(
// deployment key
{ deploymentKey: "abcd1234" }
)(App);
// In AppCenter:
// - Upload new JavaScript bundle
// - Target staging (test) or production
// - Gradual rollout: 1% → 100%
// - Users download on next app launch or check
Firebase Remote Config
val remoteConfig = Firebase.remoteConfig
remoteConfig.fetchAndActivate().addOnCompleteListener { task ->
if (task.isSuccessful) {
val forceUpdate = remoteConfig.getBoolean("force_update")
if (forceUpdate) {
// Prompt user to update from App Store
showUpdateDialog()
}
// A/B test features
val newFeatureEnabled = remoteConfig.getBoolean("new_feature")
if (newFeatureEnabled) {
enableNewFeature()
}
}
}
App Store Policies
Apple App Store
- Review time: 24-48 hours (can be <24h)
- Rejection common reasons: crashes, external links, payment outside IAP
- Private APIs not allowed
- Template apps rejected
- Subscriptions must disclose (show pricing)
Google Play Store
- Review time: 2-4 hours typical
- Staged rollout (1% default)
- Less strict than Apple
- Policy violations: spam, malware, policy abuse
Handling Rejections
# Checklist to avoid rejections:
- [ ] App doesn't crash on startup
- [ ] Privacy policy linked
- [ ] Subscription pricing shown clearly
- [ ] No external payment methods
- [ ] All promised features work
- [ ] Age-appropriate content rating
- [ ] No test data left in release build
- [ ] Proper permissions usage (don't ask for more than needed)
- [ ] Tested on min and max OS versions
- [ ] Screenshots and description accurate
Design Review Checklist
- Is beta testing set up (TestFlight/Play Console)?
- Are gradual rollout metrics defined (crash rate, ANR)?
- Is rollback procedure documented?
- Does app support min OS version?
- Are breaking changes documented?
- Is migration guide provided if needed?
- Are all promised features working?
- Is privacy policy linked?
- Are screenshot/store listing accurate?
- Is team trained on app store policies?
When to Use / When Not to Use
Gradual Rollout When:
-
10,000 active users
- Feature impact unknown
- Major changes
- Depends on third-party service
Direct 100% Release If:
- Bug fix with low risk
- Small user base
- Internal app only
Showcase: Release Timeline
Day 1: Submit to App Store
Day 2: App Store review completes
Kick off 1% rollout (monitor)
Day 3: 1% metrics healthy → expand to 5%
Day 4: Monitor 5%
Day 5: 5% metrics healthy → expand to 25%
Day 6: Monitor 25%
Day 7: 25% metrics healthy → release to 100%
Day 8: Full rollout complete, monitor
Day 10: Remove previous version from archive (if rollback not needed)
Self-Check
- Why use gradual rollout instead of releasing to 100% immediately?
- What metrics would signal you need to rollback?
- How would you handle a user base split between iOS 14 and iOS 13?
Next Steps
- Apple TestFlight ↗️
- Learn about Google Play Console ↗️
- Study CodePush for OTA Updates ↗️
- Explore Firebase Remote Config ↗️
One Takeaway
Beta testing catches issues before millions of users. Gradual rollouts prevent disasters (1% catch issues vs 100%). Monitor crash rate, ANR, session length. Plan for 3-5 day release cycle (review + testing). Have rollback procedures ready.
References
- Apple TestFlight
- Google Play Console
- CodePush for React Native
- Firebase Remote Config
- Apple App Store Review Guidelines