Building a Flutter app is only half the journey. Getting it into users' hands requires navigating the platform-specific release processes for both the Apple App Store and the Google Play Store. Doing it right from the start saves significant time and avoids repeated rejections.
Pre-Publication Checklist
Before building a release, ensure the following are in order:
- Correct app version and build number in
pubspec.yaml - App icon and splash screen configured
- Platform permissions declared
- App signed with the appropriate certificate
- Code obfuscation configured
- All tests passing
- Release build tested on a real device
Versioning
Flutter version numbers live in pubspec.yaml.
name: my_app
description: A great Flutter app
version: 1.2.3+45
The format is major.minor.patch+build:
| Part | Description | Example |
|---|---|---|
major | Breaking changes | 1 |
minor | New features, backward compatible | 2 |
patch | Bug fixes | 3 |
build | Internal build number (integer, increments each release) | 45 |
The build number must be higher than the previously uploaded build on every new submission to the stores.
App Icons and Splash Screens
App Icons
Use the flutter_launcher_icons package to generate icons for all required sizes automatically.
flutter pub add dev:flutter_launcher_icons
Add configuration to pubspec.yaml:
flutter_launcher_icons:
android: true
ios: true
image_path: "assets/icon/icon.png"
min_sdk_android: 21
web:
generate: true
image_path: "assets/icon/icon.png"
Generate the icons:
flutter pub run flutter_launcher_icons:main
Splash Screens
Use flutter_native_splash to configure native splash screens that appear before Flutter loads.
flutter pub add dev:flutter_native_splash
flutter_native_splash:
color: "#ffffff"
image: assets/splash.png
android_12:
image: assets/splash.png
color: "#ffffff"
flutter pub run flutter_native_splash:create
App Permissions
Permissions inform users what device capabilities the app requires and are reviewed during store submission.
Android
Declare permissions in android/app/src/main/AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
...
</manifest>
iOS
Add usage descriptions to ios/Runner/Info.plist:
<key>NSCameraUsageDescription</key>
<string>This app uses the camera to scan QR codes.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>This app uses location to show nearby stores.</string>
Always provide a clear, honest description — vague descriptions are a common rejection reason on iOS.
Signing the App
Signing proves that the app comes from a trusted source and has not been tampered with.
Android Signing
Step 1 — Generate a keystore:
keytool -genkey -v -keystore ~/my-release-key.jks \
-keyalg RSA -keysize 2048 -validity 10000 \
-alias my-key-alias
Step 2 — Reference the keystore in android/key.properties:
storePassword=your_store_password
keyPassword=your_key_password
keyAlias=my-key-alias
storeFile=/path/to/my-release-key.jks
Step 3 — Configure android/app/build.gradle:
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
android {
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile file(keystoreProperties['storeFile'])
storePassword keystoreProperties['storePassword']
}
}
buildTypes {
release {
signingConfig signingConfigs.release
}
}
}
iOS Signing
iOS signing is managed by Xcode and the Apple Developer portal.
iOS Signing Process
-------------------
1. Generate a Certificate Signing Request (CSR)
(Keychain Access --> Certificate Assistant --> Request a Certificate)
|
2. Create Development and Distribution Certificates
(developer.apple.com --> Certificates, Identifiers & Profiles)
|
3. Create an App Identifier (Bundle ID)
|
4. Create a Provisioning Profile
(links the App ID, certificates, and registered devices)
|
5. Install the profile in Xcode
Code Obfuscation
Obfuscation makes your compiled code significantly harder to reverse-engineer.
Android
flutter build apk --release --obfuscate --split-debug-info=./debug-info/android
iOS
flutter build ios --release --obfuscate --split-debug-info=./debug-info/ios
Store the debug info directory securely — it is required to decode any crash stack traces from obfuscated builds.
Testing Before Release
Unit Tests
flutter test
Integration Tests
Use Firebase Test Lab or run on real devices:
flutter drive --target=test_driver/app.dart
Always test the release build (not debug) on real devices for both iOS and Android before submitting. Release builds behave differently — they are AOT-compiled and obfuscated, which can surface issues that do not appear in debug builds.
Building Release Packages
Android
Build a signed APK:
flutter build apk --release
Build a signed App Bundle (preferred by Google Play):
flutter build appbundle --release
AAB vs APK:
| Format | Description |
|---|---|
| AAB (App Bundle) | Google Play generates optimized APKs per device. Preferred format. Smaller download sizes. |
| APK | Traditional package. Required for direct distribution outside Google Play. |
iOS
flutter build ios --release
Then archive and upload via Xcode's Organizer.
Submitting to the App Stores
Google Play (Android)
- Go to Google Play Console
- Create an application record
- Complete all store listing metadata (title, description, screenshots, privacy policy)
- Upload the
.aabfile in the Production track - Submit for review
The Google Play Console also provides:
- Pre-launch testing with Firebase Test Lab
- In-app purchases management
- Performance monitoring and crash reporting
- Analytics and user behavior tracking via Google Analytics
Apple App Store (iOS)
- Log in to App Store Connect
- Create a new App Record (requires a registered Bundle ID)
- Fill in metadata: name, subtitle, description, keywords, screenshots
- Archive the app in Xcode and upload via the Organizer
- Select the build in App Store Connect and submit for review
Integrating Analytics
Firebase Analytics for Android
flutter pub add firebase_analytics
Configure in pubspec.yaml and follow the Firebase setup steps. Firebase Analytics provides:
- User demographics (location, age, interests)
- Behavior flows (screen visits and navigation paths)
- Real-time active user counts
- Conversion tracking (sign-ups, purchases, custom events)
import 'package:firebase_analytics/firebase_analytics.dart';
final analytics = FirebaseAnalytics.instance;
// Log a custom event
await analytics.logEvent(
name: 'purchase_complete',
parameters: {'item_id': 'sku_001', 'value': 9.99},
);
Release Summary
Prepare
--> Update version in pubspec.yaml
--> Configure icons and splash screens
--> Declare platform permissions
Build
--> Sign (keystore for Android, certificates for iOS)
--> Obfuscate (--obfuscate --split-debug-info)
--> Build release package (appbundle / ios)
Test
--> Run all tests
--> Test release build on real devices
Submit
--> Google Play Console (Android)
--> App Store Connect (iOS)
Publishing is not a one-time event. Each update follows the same cycle — version bump, build, test, and submit. Automating this with CI/CD (GitHub Actions, Fastlane, Codemagic) saves significant time once your app is in active development.