1. Platform Setup
  2. /
  3. Android Setup

Android Setup

On this page

Blaze builds your Flutter app as a release App Bundle (.aab), signs it with your upload keystore and uploads it to Google Play. This page covers what your Flutter project needs. The Play side, meaning the app record and the service account, is covered in Google Play Console.

Before You Start

You need:

  • A Google Play developer account, with the app created in Play Console.
  • The app's package name. It is the applicationId in android/app/build.gradle.kts (or android/app/build.gradle), and it must match both the app in Play Console and the Package name in the app's Android settings in Blaze.
  • An upload keystore (next section) and a Google Play service account (Google Play Console).
  • The owner or admin role in the Blaze workspace to upload keys. Developers can deploy, but only owners and admins can change signing.

Create an Upload Keystore

If the app is already on Google Play, use the upload keystore Play already has on record. A bundle signed with a different key is rejected at upload.

To create a new one:

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

Note the keystore password and the alias (upload in the command above). If keytool asks for a separate key password, note that too. Back the keystore up and keep it out of your repository: Play only accepts builds signed with this key.

Sign Release Builds

Before every Android build, Blaze writes android/key.properties on the build runner from the keystore you uploaded:

storePassword=<your keystore password>
keyPassword=<your key password>
keyAlias=<your alias>
storeFile=/tmp/upload-keystore.jks

Your Gradle file only has to load that file into the release signing config. Flutter's project template signs release builds with the debug key, and Google Play refuses a bundle signed with the debug key, so this change is required.

In android/app/build.gradle.kts (what new Flutter projects create):

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

plugins {
    // Keep your existing plugins.
}

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

android {
    // Keep your existing settings.

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

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

In android/app/build.gradle:

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

android {
    // Keep your existing settings.

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

    buildTypes {
        release {
            signingConfig signingConfigs.release
        }
    }
}

Add android/key.properties and your keystore to .gitignore. Blaze writes its own key.properties on the runner and replaces any copy in the repository. To build a signed release on your own machine, create a local android/key.properties that points storeFile at your keystore.

Flavors are not supported. Blaze runs flutter build appbundle --release without --flavor and expects build/app/outputs/bundle/release/app-release.aab. An app that declares productFlavors does not produce that file, so every Android build fails. Keep one flavor-less release build, and use environment variables for values that differ between builds.

Version and Package Name

The version comes from the version: line of pubspec.yaml at the commit being built, for example version: 1.4.0. Anything after a + is ignored, because Blaze assigns the build number. On the runner it rewrites that line to version: 1.4.0+<build number>, and Flutter hands both values to Gradle. Keep the template's defaults in defaultConfig:

defaultConfig {
    applicationId = "com.example.app"
    // ...
    versionCode = flutter.versionCode
    versionName = flutter.versionName
}
defaultConfig {
    applicationId "com.example.app"
    // ...
    versionCode flutter.versionCode
    versionName flutter.versionName
}

A hard-coded versionCode gives every build the same version code, and Google Play refuses a version code it has already received.

The applicationId must equal the Package name in Blaze: open the application page, then the Google Play card's ··· menu, then Android settings…. Before each build, Blaze compares the two and stops the deployment at Prepare runner if they differ, so a build never uploads to the wrong app.

Connect Signing in Blaze

Each app has its own upload keystore and Play service account, and a new app starts with neither. Open Signing & keys from the application page's ··· menu.

If you set the app up with blaze init and chose Android, it already offered to send the keystore your android/key.properties names: it lists the keystore file, its alias and the passwords it would send, and uploads them only if you say yes. Otherwise:

1

Upload the keystore

In the Upload keystore card, fill in Alias and Keystore password. Fill in Key password only if your key has its own password; left blank, it is the keystore password. Then click Replace keystore and choose your .jks or .keystore file. The form saves as soon as you pick the file, so fill in the fields first.

Blaze stores the keystore and passwords encrypted and does not open the keystore when you save it. The next Android build proves the keystore signs.

2

Upload the Play service account

In the Play service account card, click Upload JSON and choose the service account's JSON key. The card then reads Uploading as the service account's email address. Google Play Console explains how to create the key and give it access.

Android builds need both. Every Android deployment checks the service account with Google before it compiles, whatever the target.

When another app in the workspace already has a keystore or a service account that works, the card for one this app is missing offers Copy from that app instead of an upload. Apps in one Google Play developer account can share its service account this way. Each app usually signs with its own upload key, though, and Google Play accepts only the upload key it has on record for the app.

How Blaze Builds Android

Each Android deployment runs on a Linux runner with Java 17 and the Flutter version from Build settings, and moves through the steps shown on the deployment page:

  1. Prepare runner: reads pubspec.yaml and android/app/build.gradle.kts (or build.gradle) from GitHub. The deployment stops here if a path: dependency points outside the repository, if the environment: flutter: constraint in pubspec.yaml rules out the Flutter version you pinned, or if the applicationId does not match the package name.
  2. Clone & pub get: checks the service account with Google, checks out the exact commit being deployed, runs flutter pub get and sets the version.
  3. Compile: writes android/key.properties and runs flutter build appbundle --release, passing your environment variables as --dart-define. Builds for Play's testing tracks include arm64 code only; production releases and draft uploads include every architecture.
  4. Code sign & export: confirms the signed app-release.aab exists.
  5. Upload → Play Console: uploads the bundle and releases it on the track you chose. See Deployment for what each track does.

Gradle, pub and Flutter SDK caches persist between deployments, so later builds are faster than the first.

What Is Not Supported

  • Product flavors. Builds run without --flavor. See Sign Release Builds.
  • A different entry point. Blaze builds lib/main.dart; there is no setting for --target.
  • APKs. Every Android build is an App Bundle.
  • Debug or profile builds. Every build is a release build.
  • Environment variables in Gradle or AndroidManifest.xml. They reach Dart only. See Environment Variables.
  • An app in a subdirectory of the repository. See Build Configuration.

Troubleshooting

The build fails at Compile or Code sign & export and no app-release.aab is produced. Check android/app/build.gradle(.kts) for productFlavors and remove them.

The keystore cannot be opened. The alias or a password does not match the keystore. Upload it again on Signing & keys with the right values, and fill in Key password if the key has its own password. You can check a keystore locally with keytool -list -v -keystore upload-keystore.jks.

The upload to Play Console fails. Check these in order:

  1. The app exists in Play Console, and its first release was uploaded by hand. Google Play does not accept uploads through its API for an app that has never had a bundle uploaded in Play Console.
  2. The package name in Blaze matches the app in Play Console.
  3. The service account was invited in Play Console with permission to release this app.
  4. The version code is higher than any Play already has. A hard-coded versionCode, or builds uploaded outside Blaze with higher version codes, cause this.
  5. The bundle is signed with the upload key Play has on record for the app.

The service account is rejected before the build starts. The JSON key may have been deleted in Google Cloud, or the Google Play Android Developer API may not be enabled for its Google Cloud project. See Google Play Console.

Next Steps