Capstone: Real-World Flutter Project

Build, Release & App Store Deployment

16 min Lesson 10 of 10

Build, Release & App Store Deployment

Shipping a Flutter app to the Google Play Store or the Apple App Store is the final milestone of any production project. This lesson walks you through the complete release pipeline: configuring code signing for Android and iOS, setting version metadata, producing optimised release builds, and navigating the submission workflows for both stores.

Versioning Your App

Flutter controls the version shown to users and the internal build counter through a single line in pubspec.yaml:

pubspec.yaml — version field

# version: <user-visible version>+<build number>
version: 1.2.0+5

# Maps to:
#   Android: versionName="1.2.0"  versionCode=5
#   iOS:     CFBundleShortVersionString=1.2.0  CFBundleVersion=5

The part before the + is the marketing version (shown in the stores). The integer after + is the build number — it must be incremented with every upload to either store. You can override these values from the CLI at build time:

Overriding version at build time

flutter build apk \
  --build-name=1.2.0 \
  --build-number=5

Android: Signing with a Keystore

Every Android APK or App Bundle must be signed with a private key. You create a keystore once and reuse it for the lifetime of your app — losing the keystore means you can never update your app on the Play Store.

1 — Generate a keystore (run once)

keytool -genkey -v \
  -keystore ~/keys/my-release-key.jks \
  -keyalg RSA -keysize 2048 \
  -validity 10000 \
  -alias my-key-alias

Store the generated .jks file and its passwords somewhere safe (a password manager or encrypted backup). Next, create android/key.properties — add this file to .gitignore immediately:

android/key.properties

storePassword=yourStorePassword
keyPassword=yourKeyPassword
keyAlias=my-key-alias
storeFile=/Users/you/keys/my-release-key.jks

Finally, update android/app/build.gradle to load those properties and apply them to the release signing config:

android/app/build.gradle — signing config

def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
    keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}

android {
    signingConfigs {
        release {
            keyAlias     keystoreProperties['keyAlias']
            keyPassword  keystoreProperties['keyPassword']
            storeFile    keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
            storePassword keystoreProperties['storePassword']
        }
    }
    buildTypes {
        release {
            signingConfig signingConfigs.release
            minifyEnabled true
            shrinkResources true
        }
    }
}
Warning: Never commit key.properties or the .jks keystore to your git repository. Anyone with those files can publish updates to your app pretending to be you. Add both to .gitignore and store them in a secure vault.

Building a Signed Android Release

Google Play requires an App Bundle (.aab) rather than a plain APK for new apps. An App Bundle lets the Play Store deliver optimised APKs per device architecture and screen density, reducing download sizes.

Build a signed App Bundle

# Preferred for Play Store submission
flutter build appbundle --release

# Output: build/app/outputs/bundle/release/app-release.aab

# For direct APK distribution (sideloading / testing)
flutter build apk --release --split-per-abi

# Output: build/app/outputs/flutter-apk/
#   app-armeabi-v7a-release.apk
#   app-arm64-v8a-release.apk
#   app-x86_64-release.apk

iOS: Certificates and Provisioning Profiles

iOS code signing requires an Apple Developer account. The process involves three artefacts:

  • Signing Certificate — proves your identity as a developer (Distribution certificate for App Store).
  • App ID (Bundle Identifier) — a unique reverse-domain string registered in the Apple Developer portal (e.g. com.example.myapp).
  • Provisioning Profile — links your certificate, App ID, and (for ad hoc) device UDIDs. Use an App Store Distribution profile for submissions.

In Xcode, open ios/Runner.xcworkspace, select the Runner target, and under Signing & Capabilities choose your team. With Automatically manage signing enabled, Xcode downloads and refreshes certificates and profiles for you.

Tip: If you work in CI/CD pipelines, use Fastlane's match action to sync certificates and profiles from a private git repo or Google Cloud Storage bucket. This keeps all machines in sync without manual Xcode interaction.

Building a Signed iOS Release

Build an iOS archive for App Store submission

# Build with Xcode signing
flutter build ipa --release

# The .ipa is placed at:
# build/ios/ipa/<AppName>.ipa

# If you need to specify the export method explicitly:
flutter build ipa \
  --export-options-plist=ios/ExportOptions.plist

Upload the resulting .ipa to App Store Connect using Xcode Organizer (Product > Archive > Distribute App) or the xcrun altool / xcrun notarytool CLI tools. Alternatively, use Transporter (a free Apple app) for a drag-and-drop upload experience.

Google Play Store Submission Checklist

  • Create a new app in the Google Play Console and fill in the store listing (screenshots, description, content rating).
  • Upload your app-release.aab to the Internal Testing track first to validate it, then promote to Production.
  • Set a rollout percentage (e.g. 10 %) for staged releases so you can catch crashes before reaching all users.
  • Ensure versionCode is strictly greater than the previous upload or the upload will be rejected.

Apple App Store Submission Checklist

  • In App Store Connect, create a new app version and fill in the What's New text, screenshots (multiple device sizes), and App Review information.
  • Upload the .ipa and wait for processing (usually 5–30 minutes).
  • Select the build in the version page and submit for review. Average review time is 24–48 hours.
  • Ensure the CFBundleVersion (build number) is strictly greater than any previously uploaded build — even rejected ones count.
Key Takeaway: A production release is a structured pipeline, not a single command. Manage your signing artefacts securely, increment version numbers consistently, prefer App Bundles on Android, and always test on the internal/testflight track before a public rollout.