Skip to content

Releasing for Android

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.

Step 1: Configure application ID and permissions

Section titled “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:

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). 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.

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, you cannot update your application.

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

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

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

To sign in CI, where the keystore isn’t on the machine, see Android code signing in the GitHub Actions guide.

Google Play requires the Android App Bundle (AAB) 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 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.

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 and Distribute an APK outside Google Play.

Upload the .aab that shorebird release android printed to the Google Play Console through one of its release tracks (see Upload your app to the Play Console):

  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.

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.

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.