Dart Tutorial

Flutter Lesson 77 of 83 5 min read

Build and Release a Flutter App for Android

Build a signed Android app bundle with Flutter and publish it on Google Play: keystore, signing config, versioning and store listing.

On this page

Publishing on Google Play has four stages: sign the app, build it, create a store listing, and upload. Store requirements change, so check the Play Console’s own guidance as you go.

Before you build #

  • App icon, name and app id are set. See the previous lesson.
  • The Internet permission is in AndroidManifest.xml if the app uses the network. Debug builds add it automatically; release builds do not.
  • version in pubspec.yaml is correct, with a build number higher than any earlier upload.
  • The app works in release mode: flutter run --release on a real device.
  • No test data, debug menus or print statements with private information remain.

1. Create an upload keystore #

Android requires every app to be signed. Create a keystore once and keep it safe.

On macOS or Linux:

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

On Windows (PowerShell):

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

You will be asked for a password and some details.

Back up the keystore file and its passwords, somewhere other than your laptop. Never commit them to Git.

2. Tell Gradle about the keystore #

Create android/key.properties:

storePassword=your-store-password
keyPassword=your-key-password
keyAlias=upload
storeFile=/Users/you/upload-keystore.jks

Add it to android/.gitignore:

key.properties
*.jks

Then edit android/app/build.gradle.kts. Load the properties near the top of the file:

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

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

And inside the android { } block, add a signing configuration and use it for release builds:

android {
    // ...

    signingConfigs {
        create("release") {
            keyAlias = keystoreProperties["keyAlias"] as String?
            keyPassword = keystoreProperties["keyPassword"] as String?
            storeFile = keystoreProperties["storeFile"]?.let { file(it) }
            storePassword = keystoreProperties["storePassword"] as String?
        }
    }

    buildTypes {
        release {
            signingConfig = signingConfigs.getByName("release")
        }
    }
}

Older projects have a build.gradle file written in Groovy, with slightly different syntax. The Flutter documentation on Android deployment shows both forms.

3. Check the app configuration #

In the same Gradle file:

defaultConfig {
    applicationId = "com.yourcompany.myshop"   // your permanent id
    minSdk = flutter.minSdkVersion              // lowest Android version supported
    targetSdk = flutter.targetSdkVersion
    versionCode = flutter.versionCode           // from pubspec.yaml
    versionName = flutter.versionName
}

Google Play requires a recent targetSdk for new apps and updates. Keeping Flutter up to date usually takes care of it.

4. Build #

Google Play wants an app bundle (.aab). It lets Play deliver only the code and resources each device needs, so downloads are smaller.

flutter build appbundle

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

To install directly on a device, or to distribute outside Play, build APKs:

flutter build apk --split-per-abi

This produces one smaller APK per processor type, in build/app/outputs/flutter-apk/.

Useful options:

flutter build appbundle --build-name=1.2.0 --build-number=7
flutter build appbundle --obfuscate --split-debug-info=build/symbols
flutter build appbundle --dart-define=ENV=prod

--obfuscate makes the compiled Dart harder to read. Keep the symbols folder: you need it to make sense of crash reports from that build.

5. Test the release build #

flutter install --release

Check in particular: network requests, anything that reads files or assets, and every plugin. Problems that appear only in release usually come from a missing permission or from code shrinking removing something that a plugin needs.

6. Create the Play Console listing #

You need a Google Play developer account, which has a one-time registration fee. Then, in the Play Console:

  1. Create app: name, default language, app or game, free or paid.
  2. Store listing: short description (80 characters), full description, app icon (512 by 512), feature graphic (1024 by 500), and screenshots for phones, plus tablets if you support them.
  3. App content: privacy policy URL, data safety form, content rating questionnaire, target audience, and whether the app has ads.
  4. Pricing and countries.

The data safety form asks what data the app collects and why. Answer accurately, including data collected by the packages you use, such as analytics and crash reporting.

7. Upload and roll out #

Play has testing tracks. Use them.

TrackAudienceUse
Internal testingUp to 100 testers you nameQuick checks. Available within minutes
Closed testingInvited groupsWider testing before launch
Open testingAnyone who opts inA public beta
ProductionEveryoneThe real release

New personal developer accounts may be required to run a closed test with a number of testers for a period before they can publish to production. The Play Console tells you what applies to your account.

Upload the .aab to a track, add release notes, and submit for review. Review takes from hours to several days.

When you first upload, enrol in Play App Signing. Google then holds the final signing key, and your keystore is only an upload key, which can be reset if you lose it.

Updates #

  1. Raise the version in pubspec.yaml: 1.2.1+8. The build number must increase.
  2. Build the bundle.
  3. Create a new release in the Play Console and upload.
  4. Consider a staged rollout: release to 10 percent of users first, watch for crashes, then widen.

Common problems #

ProblemLikely cause
No data in release, works in debugMissing Internet permission
“Version code already used”Increase the number after +
“Signed in debug mode”The release signing config is not applied
Crashes only in releaseCode shrinking removed a class. Check the plugin’s ProGuard rules
Rejected by reviewPrivacy policy, permissions or data safety answers. The email says which

Try it yourself #

Create a keystore and configure signing for one of your apps. Build an app bundle and a set of split APKs, install the release APK on a phone, and compare its size and start-up speed with the debug build.

Practise in the playground Updated by Santosh Adhikari