Building a Signed Flutter Android App Bundle on Windows (Groovy and Kotlin DSL)

Reading Time: 3 minutes

If you’re prepping a Flutter app for Play Store release on Windows, you need a signed Android App Bundle (.aab), not the debug build Flutter gives you out of the box. This post walks through generating a production keystore, wiring it into your Gradle config, and building the bundle. It covers both build.gradle (Groovy) and build.gradle.kts (Kotlin DSL). Since Flutter projects created more recently default to Kotlin DSL, the syntax trips people up.

Step 1: Generate a keystore

You need Java installed for this (it ships with Android Studio). Open Command Prompt and run:

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

keytool will prompt you interactively for a few things. Two of them matter a lot for the next step:

Enter keystore password:
Re-enter new password:
...
Enter key password for <upload>
        (RETURN if same as keystore password):

Whatever you type at “Enter keystore password” becomes your storePassword. Whatever you type at “Enter key password” becomes your keyPassword. If you just hit Enter at the second prompt (the default, and what most people do), your keyPassword is identical to your storePassword.

There’s no way to recover these later. keytool doesn’t expose stored passwords. If you lose them after publishing to Play Store, you’re stuck unless you go through Google’s key upgrade process for Play App Signing. Put both passwords in a password manager the moment you generate the keystore, not after.

Also, keep the keystore path short. Windows has path length limits, and deeply nested project folders, combined with a long keystore path, can cause errors that appear unrelated to the actual problem.

Step 2: Create key.properties

In your Flutter project root, create android/key.properties:

storePassword=<value from "Enter keystore password">
keyPassword=<value from "Enter key password">
keyAlias=upload
storeFile=C:\keystore\upload-keystore.jks

Use double backslashes for the Windows path, or forward slashes. Both parse fine.

Add this file to .gitignore immediately. It should never end up in version control.

Step 3: Wire it into Gradle

This is where Groovy and Kotlin DSL diverge.

If you have android/app/build.gradle (Groovy)

Add this near the top of the file, before the android { } block:

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

Then inside android { }:

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
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

If you have android/app/build.gradle.kts (Kotlin DSL)

At the top of the file, before android { }:

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

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

Then inside android { }, alongside your existing compileSdk, defaultConfig, etc.:

android {
    // ... existing config

    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")
            isMinifyEnabled = true
            isShrinkResources = true
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
}

A few things that catch people switching from Groovy:

  • Signing configs are declared with create("release") instead of just release { }.
  • Every property assignment needs an =. Kotlin is statically typed; Groovy’s dynamic property setters don’t apply here.
  • Boolean properties get an is prefix: minifyEnabled true becomes isMinifyEnabled = true.
  • keystoreProperties["key"] returns Any?, so you need explicit casts like as String? to satisfy the compiler.

Step 4: Build the bundle

Same command regardless of which Gradle syntax you’re using:

flutter clean
flutter pub get
flutter build appbundle --release

Output lands at:

buildappoutputsbundlereleaseapp-release.aab

Upload that file to the Google Play Console.

Windows-specific gotchas

  • keytool not found: add Java’s bin folder to your PATH. If you’re using Android Studio’s bundled JBR, it’s usually at C:Program FilesAndroidAndroid Studiojbrbin.
  • Keystore not found errors: almost always a backslash escaping issue in key.properties. Double-check \ vs .
  • Duplicate version code on upload: bump the version in pubspec.yaml (e.g. 1.0.1+2) before every release build. Play Console rejects re-uploads with the same version code.

That covers the full path from keystore generation to a signed .aab ready for Play Store, on both Gradle syntaxes.

Afolabi 'aphoe' Legunsen on FacebookAfolabi 'aphoe' Legunsen on GithubAfolabi 'aphoe' Legunsen on GoogleAfolabi 'aphoe' Legunsen on LinkedinAfolabi 'aphoe' Legunsen on TwitterAfolabi 'aphoe' Legunsen on Youtube
Afolabi 'aphoe' Legunsen
Software Project Lead at itquette solutions
A software developer with 9 years professional experience. Over the years, he has built and contributed to a number of projects which includes MoneyTalks that won Visa's Financial Literacy Challenge and Opomulero that won the British Council's Culture Shift III.

He currently the Software Project Lead of Itquette Solutions where his team has built Smart PMS and BlueQuiver HRMS among many other products.

Leave a Comment