Shipping to the App Store or Play Store is a checklist, not a mystery. Work through each step in order and your app lands on both stores.

Android is the friendlier of the two. Start there if it's your first release. iOS has more gates (certificates, provisioning profiles, a stricter review), but the path is well-worn once you've done it a couple of times.

Android

1. App icon

Put a 1024x1024 PNG at assets/icon.png. Install flutter_launcher_icons and add to pubspec.yaml:

yaml
flutter_launcher_icons:
android: "launcher_icon"
ios: false
image_path: "assets/icon.png"
adaptive_icon_foreground: "assets/icon.png"
adaptive_icon_background: "#FFFFFF"

Run the generator and Android adaptive icons show up on your home screen.

bash
dart run flutter_launcher_icons

2. App ID

Every app needs a globally unique ID. Flutter defaults to com.example.yourapp, which you must change before submitting. Edit android/app/build.gradle:

kotlin
android {
namespace = "app.yourname.yourapp"
defaultConfig {
  applicationId = "app.yourname.yourapp"
  minSdk = 23
  targetSdk = 35
}
}

Google bumps the required targetSdk every year. If Play rejects your build for targeting an older SDK, this is where you raise the number.

3. Keystore

Every Android release has to be signed. Generate a keystore once and use it forever.

bash
keytool -genkey -v -keystore ~/upload-keystore.jks \
-keyalg RSA -keysize 2048 -validity 10000 \
-alias upload

Back up the .jks file and the passwords to a password manager before you do anything else. Lose them and you can't update your app. Ever. New version means a new listing.

Create android/key.properties (and add it to .gitignore):

dart
storePassword=...
keyPassword=...
keyAlias=upload
storeFile=/Users/you/upload-keystore.jks

Reference it in android/app/build.gradle:

kotlin
def keystoreProperties = new Properties()
def f = rootProject.file('key.properties')
if (f.exists()) keystoreProperties.load(new FileInputStream(f))

signingConfigs {
release {
  keyAlias keystoreProperties['keyAlias']
  keyPassword keystoreProperties['keyPassword']
  storeFile keystoreProperties['storeFile']
    ? file(keystoreProperties['storeFile']) : null
  storePassword keystoreProperties['storePassword']
}
}
buildTypes {
release { signingConfig signingConfigs.release }
}

4. Build

Google Play wants an AAB (Android App Bundle), not an APK.

bash
flutter build appbundle --release

The output is at build/app/outputs/bundle/release/app-release.aab.

5. Play Console

Register at play.google.com/console. There's a one-time $25 fee.

Create a new app, then work through the dashboard checklist: privacy policy, app access, ads, content rating, target audience, data safety. Each one has a form to fill in.

Fill the store listing: category, short and full description, screenshots, contact email. Upload the .aab to Production, create a release, and submit. Review takes hours to a couple of days.

iOS

1. Requirements

  • A Mac with Xcode installed (you can't build iOS apps on Windows or Linux).
  • An Apple Developer account ($99/year).

2. App icon

Configure the same flutter_launcher_icons for iOS. iOS icons can't have transparency, so remove_alpha_ios: true fills transparent pixels with white.

yaml
flutter_launcher_icons:
android: false
ios: true
image_path: "assets/icon.png"
remove_alpha_ios: true

3. Bundle ID

Open ios/Runner.xcworkspace in Xcode. Under Runner → General, set the Bundle Identifier to something in reverse-domain format (app.yourname.yourapp). This is the permanent ID for your app on the App Store.

4. Signing

In Xcode, Signing & Capabilities → check "Automatically manage signing" → pick your team. Xcode creates the certificates and provisioning profiles for you. If it errors out, Xcode → Settings → Accounts, make sure you're signed in with your Developer Program Apple ID.

5. Build

bash
flutter build ipa --release

6. App Store Connect

Go to appstoreconnect.apple.com and create a new app. Pick iOS for the platform, fill in the name, pick your Bundle ID, and give it an SKU (any unique string for your records).

7. Upload

In Xcode: Window → Organizer → select your archive → Distribute App → App Store Connect → Upload. Or download Transporter from the Mac App Store and drag the .ipa file in.

The build takes a few minutes to process. It appears in App Store Connect under TestFlight once it's ready.

8. Listing

Fill in screenshots (at minimum 6.7" and 5.5" iPhone), description, keywords (100 char limit), support URL, privacy policy URL, category.

Under App Review Information, give Apple any credentials they need to test your app. If your app needs a login, create a test account for them.

9. Submit

Select the uploaded build, click Submit for Review. Apple reviews in 24 to 48 hours.

Common rejection reasons: missing privacy policy, unclear app purpose, login required without test credentials, broken features. Fix and resubmit.