EthicsComputerCourses
Join training +91 90532 10052

Build & Publish to the Stores

Flutter & Dart · Lesson 27 of 29 Advanced 9 min read Updated

Running your app with flutter run is not the same as shipping it. A release build must have a permanent app ID, a version number, an icon, a digital signature that proves it comes from you, and it must pass the store's review. This lesson is an overview of the whole path to the Google Play Store and the Apple App Store, with the commands you will use.

Store rules, fees and forms change often, so treat this lesson as a map and always follow the current official guides: the Flutter deployment pages on docs.flutter.dev, the Play Console Help Centre and Apple's App Store Connect help.

Before any release

Go through this checklist for both platforms:

  1. App ID. Android's applicationId (in android/app/build.gradle.kts) and iOS's bundle identifier, such as in.meera.expense_tracker. Once the app is on a store, the ID can never change.
  2. App name. The name under the icon: android:label in AndroidManifest.xml and the display name in Xcode (or CFBundleDisplayName in Info.plist).
  3. Version. version: 1.2.0+5 in pubspec.yaml. The part before + is the version users see; the build number after + must increase with every upload. You can override both: flutter build appbundle --build-name=1.2.0 --build-number=5.
  4. Icon. Replace the default Flutter icon. The flutter_launcher_icons package generates every size from one image.
  5. Permissions. Only what you use, with clear iOS usage descriptions (see Platform Permissions), and INTERNET in the main Android manifest.
  6. Tests and analysis. flutter analyze with no issues and flutter test passing.
  7. Privacy policy. Both stores ask for a privacy policy URL and a description of the data you collect.

Then test a release build on a real phone: flutter run --release. Release builds are compiled ahead of time and behave slightly differently from debug builds, so problems such as a missing permission only appear there.

Android: signing with an upload key

Google Play needs your app to be signed. You create an upload key once and keep it safe forever. With Play App Signing, Google holds the final app-signing key, and your upload key proves that new uploads come from you. On Windows (PowerShell):

PowerShell
keytool -genkey -v -keystore $env:USERPROFILE\upload-keystore.jks `
        -storetype JKS -keyalg RSA -keysize 2048 -validity 10000 `
        -alias upload

keytool is part of the Java Development Kit (JDK); Android Studio includes one, and flutter doctor -v shows which Java installation Flutter uses. The command asks for passwords and your name or organisation. Then create android/key.properties:

Properties
storePassword=your-store-password
keyPassword=your-key-password
keyAlias=upload
storeFile=C:\\Users\\meera\\upload-keystore.jks

On Windows, write the path with double backslashes as shown. Finally, edit android/app/build.gradle.kts so the release build uses this key:

Kotlin
import java.io.FileInputStream
import java.util.Properties

val keystoreProperties = Properties()
val keystorePropertiesFile = rootProject.file("key.properties")
if (keystorePropertiesFile.exists()) {
    keystoreProperties.load(FileInputStream(keystorePropertiesFile))
}

android {
    // namespace, compileSdk, defaultConfig ... as generated

    signingConfigs {
        create("release") {
            keyAlias = keystoreProperties.getProperty("keyAlias")
            keyPassword = keystoreProperties.getProperty("keyPassword")
            storeFile = keystoreProperties.getProperty("storeFile")?.let { file(it) }
            storePassword = keystoreProperties.getProperty("storePassword")
        }
    }
    buildTypes {
        release {
            signingConfig = signingConfigs.getByName("release")
        }
    }
}

The generated file signs release builds with the debug key so that flutter run --release works out of the box; this change replaces that line. Keep the plugins { } block at the top of the file as generated.

Android: build an App Bundle and upload

Terminal
flutter build appbundle
flutter build apk --split-per-abi

flutter build appbundle creates an Android App Bundle (.aab) in build/app/outputs/bundle/release/. Google Play uses the bundle to generate optimised APKs for each device, so users download less. New apps on Google Play must be published as App Bundles. flutter build apk --split-per-abi creates separate APK files for each processor type in build/app/outputs/flutter-apk/; APKs are useful for sharing test builds directly or for stores that do not accept bundles.

Then, in the Google Play Console:

  1. Create a developer account (there is a one-time registration fee; check the current amount in the Play Console) and complete identity verification.
  2. Create the app, fill in the store listing: title, short and full description, screenshots, feature graphic, category and contact details.
  3. Complete the App content forms: privacy policy, data safety (what data you collect and why), content rating questionnaire, target audience and ads declaration.
  4. Upload the .aab to a testing track first (internal, then closed testing), install it from the Play Store on test phones, and fix problems.
  5. Promote the release to Production and submit it for review. Review usually takes from hours to several days.

Google Play also requires apps to target a recent Android API level; Flutter's template sets targetSdk = flutter.targetSdkVersion, which current Flutter versions keep up to date. New personal developer accounts may have to run a closed test with a minimum number of testers for a set period before they can publish to production; the Play Console shows the current requirement.

iOS: build, TestFlight and App Store review

iOS releases need a Mac with Xcode and membership of the Apple Developer Program (an annual fee; see Apple's website for the current price in India).

  1. Open ios/Runner.xcworkspace in Xcode. Under Signing & Capabilities, choose your team and set the bundle identifier. Xcode can manage certificates and provisioning profiles automatically.
  2. In App Store Connect, create a new app record with the same bundle identifier, name, primary language and SKU.
  3. Build the release archive from the terminal:
Terminal
flutter build ipa

This creates an Xcode archive and an .ipa file under build/ios/. Upload the .ipa with Apple's Transporter app, or open the archive in Xcode's Organizer and click Distribute App.

  1. The build appears in App Store Connect after processing. Add internal or external testers in TestFlight to test it on real iPhones.
  2. Fill in the App Store listing (screenshots for the required device sizes, description, keywords, privacy "nutrition label" and age rating), choose the build and Submit for Review.

Apple's App Review checks the app against its guidelines. Common rejection reasons are crashes, broken links, placeholder content, missing login details for the reviewer, and vague permission descriptions. Reply in App Store Connect, fix the issue and resubmit.

Shrinking and protecting the release

  • flutter build appbundle --obfuscate --split-debug-info=build/symbols makes the Dart code harder to reverse-engineer. Keep the build/symbols folder for that version; you need it to read crash stack traces.
  • flutter build apk --analyze-size (and the same flag for other targets) shows what takes space in your app.
  • Remember: obfuscation does not make secrets safe. Never ship API secrets in the app.

After launch, watch the crash and ANR (app not responding) reports in Play Console and the crash reports in Xcode Organizer or App Store Connect, answer user reviews, and ship updates with a higher build number.

Common mistakes

  • Uploading with the same build number. Both stores reject a build number they have already seen.
  • Committing the keystore or key.properties to a public repository.
  • Testing only debug builds. Always install and try the release build before submitting.
  • Changing the app ID after publishing. The store treats it as a completely different app.
  • Incomplete store forms. Missing privacy policy or data safety answers block the release.

Key points

  • Set a permanent app ID, app name, version (name+build), icon and minimal permissions before releasing.
  • Android: create an upload keystore, reference it from key.properties and build.gradle.kts, and build with flutter build appbundle.
  • Google Play Console: listing, app content forms, testing tracks, then production review.
  • iOS: Apple Developer Program, Xcode signing, flutter build ipa, upload, TestFlight, then App Review.
  • Increase the build number for every upload, keep keys secret and backed up, and test release builds on real devices.

Exercise

Your expense tracker is at version: 1.0.0+1 and already live on Google Play. You fixed a bug and added a new chart screen. Decide the new version, write the pubspec.yaml line, and write the commands to (a) build a signed App Bundle with obfuscation, and (b) build an iOS archive with the same version. Then list three things you would check before submitting.

Show solution
YAML
version: 1.1.0+2
Terminal
flutter test
flutter build appbundle --obfuscate --split-debug-info=build/symbols
flutter build ipa --obfuscate --split-debug-info=build/symbols

A new feature without breaking changes is a minor release, so the version becomes 1.1.0, and the build number must go up to 2. Before submitting, check that the release build installs and runs on a real phone, that the store screenshots and "What's new" text mention the chart screen, and that build/symbols is saved for reading crash reports of this version.

Quick Quiz

1. What does version: 2.3.1+14 in pubspec.yaml mean?

2. Which file format do new apps upload to Google Play?

3. Why must the upload keystore never be committed to a public repository?

4. What do you need to publish an iOS app?

Learn Flutter & Dart with a mentor

Free lessons get you started. Our Mobile App Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call