---
title: "Releasing for Android"
description: "Configure your application ID and upload key, build a signed app bundle with the Shorebird CLI, and publish it to Google Play so it can receive patches."
---

This page takes a Flutter app from its first signing setup to a build on Google Play that can receive Shorebird patches. Steps 1 to 3 are the same one-time setup any Flutter app needs. Step 4 builds the app bundle with `shorebird release android`, which you then upload to Google Play.

Upload the build `shorebird release` made

Patches only install on the exact build `shorebird release android` produced and uploaded to Shorebird. Don’t upload an app bundle from `flutter build appbundle`, and don’t rebuild with Gradle or Android Studio after `shorebird release` runs. A build made any other way either doesn’t include Shorebird or doesn’t match the release, and no patch will install on it. See [Patches can’t install because the app isn’t the build Shorebird released](/code-push/troubleshooting/#patches-cant-install-because-the-app-isnt-the-build-shorebird-released).

## Step 1: Configure application ID and permissions

Open `android/app/build.gradle.kts` (Kotlin DSL, which `flutter create` generates since Flutter 3.29) or `android/app/build.gradle` (Groovy, in older projects) and check the `applicationId`, `minSdk`, and `targetSdk`:

* build.gradle.kts

  ```
  defaultConfig {
      applicationId = "com.example.myapp"
      minSdk = flutter.minSdkVersion
      targetSdk = flutter.targetSdkVersion
      versionCode = flutter.versionCode
      versionName = flutter.versionName
  }
  ```

* build.gradle

  ```
  defaultConfig {
      applicationId = "com.example.myapp"
      minSdk = flutter.minSdkVersion
      targetSdk = flutter.targetSdkVersion
      versionCode = flutter.versionCode
      versionName = flutter.versionName
  }
  ```

`versionCode` and `versionName` come from the `version` in `pubspec.yaml` (see [App versioning and build numbers](/flutter-concepts/releasing-flutter-apps/#app-versioning-and-build-numbers)). Shorebird reads the release version from the app bundle it builds. Keeping Flutter’s values there keeps that version matching `pubspec.yaml`, and lets `--build-name` and `--build-number` override it.

Ensure all runtime permissions requested in `android/app/src/main/AndroidManifest.xml` comply with [Google Play Developer Policy](https://play.google.com/about/developer-content-policy/).

## Step 2: Create an upload keystore

Google Play uses digital certificates to verify the author of an application. Create a private signing key using the `keytool` utility:

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

Keep this keystore file secure and backed up. If you lose your keystore and do not use [Google Play App Signing](https://support.google.com/googleplay/android-developer/answer/9842756), you cannot update your application.

## Step 3: Configure signing in Gradle

`shorebird release android` runs Flutter’s Gradle build, so it signs the app bundle with whatever release signing config your Gradle file sets up.

Create a file named `android/key.properties` to reference the keystore:

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

Caution

Add `key.properties` and all `*.jks` / `*.keystore` files to your `.gitignore` to prevent leaking private signing credentials into source control.

Load `key.properties` in your app’s Gradle file and use it for release builds:

* build.gradle.kts

  ```
  // Imports go at the very top of the file, above plugins { }.
  import java.io.FileInputStream
  import java.util.Properties

  plugins {
      // ...
  }

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

  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")
          }
      }
  }
  ```

* build.gradle

  ```
  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
          }
      }
  }
  ```

To sign in CI, where the keystore isn’t on the machine, see [Android code signing](/code-push/ci/github/#android-code-signing) in the GitHub Actions guide.

## Step 4: Create a Shorebird release

Google Play requires the [Android App Bundle (AAB)](https://developer.android.com/guide/app-bundle) format for new apps. `shorebird release android` builds a signed app bundle with Shorebird’s Flutter, and uploads its compiled Dart code to Shorebird as the base that later patches apply to:

```
shorebird release android
```

When it finishes, it prints the path of the app bundle to upload, by default `build/app/outputs/bundle/release/app-release.aab`. With a flavor, the path includes the flavor name.

`shorebird release` takes the common `flutter build` options, such as `--flavor`, `--target`, `--build-name`, `--build-number` and `--dart-define`, and passes any other `flutter build` argument you put after a `--` separator. See [Create a Release](/code-push/release/) for those, and run `shorebird release --help` for every option.

To obfuscate your Dart code, pass `--obfuscate`, and `--split-debug-info` with a directory to keep the symbol files in:

```
shorebird release android --obfuscate --split-debug-info=build/symbols
```

Patches to an obfuscated release are obfuscated the same way automatically. See [Code obfuscation and debug symbols](/flutter-concepts/releasing-flutter-apps/#code-obfuscation-and-debug-symbols).

### Build an APK for testing or other stores

To get an APK as well as the app bundle, for distribution outside Google Play or for QA, pass `--artifact apk`:

```
shorebird release android --artifact apk
```

Shorebird doesn’t support Flutter’s `--split-per-abi`, and stops with:

```
Shorebird does not support --split-per-abi: each split APK gets a different version than the one in your pubspec.yaml.
```

To build an APK for one architecture instead, pass `--target-platform`:

```
shorebird release android --artifact apk --target-platform android-arm64
```

`--target-platform` applies to the app bundle as well as the APK, so the bundle from this release only runs on 64-bit ARM devices. Use it for APKs you distribute yourself, and don’t upload that app bundle to Google Play.

See [Build a smaller APK for one architecture](/code-push/release/#build-a-smaller-apk-for-one-architecture) and [Distribute an APK outside Google Play](/code-push/release/#distribute-an-apk-outside-google-play).

## Step 5: Upload to Google Play

Upload the `.aab` that `shorebird release android` printed to the [Google Play Console](https://play.google.com/console/about/) through one of its release tracks (see [Upload your app to the Play Console](https://support.google.com/googleplay/android-developer/answer/9859152)):

1. **Internal testing**: Instant distribution to up to 100 designated testers without full store review delays.
2. **Closed testing (Alpha/Beta)**: Testing with larger defined groups or organizations.
3. **Open testing**: Public pre-release track with store visibility.
4. **Production**: General availability for all users. Supports **staged rollouts** (e.g., releasing to 10%, 20%, then 100% of users over several days to monitor crash rates).

You can release the same app bundle on several tracks, for example by promoting it from internal testing to production. Devices that install it from any track receive patches for that release.

## Step 6: Ship fixes as patches

When you change Dart code in this version later, ship the change as a patch instead of a new store build:

```
shorebird patch android --track=stable
```

A change to native code, a plugin with native code, assets such as images and fonts, or the Flutter version needs a new store release: bump the `version` in `pubspec.yaml`, then repeat steps 4 and 5. See [Create a Patch](/code-push/patch/).

## Building without Shorebird

For background: a Flutter app that doesn’t use Shorebird builds its store bundle with `flutter build appbundle`, which writes the same `build/app/outputs/bundle/release/app-release.aab` path. That build uses the standard Flutter engine, which has no Shorebird updater, so it can never receive a patch. Once your app uses Shorebird, use `shorebird release android` for every build you upload to Google Play.
