For a Flutter app's first Google Play release, why build with flutter build appbundle rather than flutter build apk, and when does --split-per-abi matter?
answer
- Play builds per-device APKs
- new Play apps must upload a bundle
- fat APK carries every ABI
- three ABIs in a release build
- split APKs bump versionCode per ABI
basics
~20 sflutter build appbundle produces an .aab from which Google Play generates per-device APKs, and new Play apps must upload a bundle; --split-per-abi matters only when shipping APKs elsewhere, replacing one fat APK with one APK per ABI.
solid answer
~40 s`flutter build appbundle` (release by default) writes `build/app/outputs/bundle/release/app.aab`, holding Dart AOT code and the engine for `armeabi-v7a`, `arm64-v8a` and `x86_64`. Play splits it and serves each device only its ABI and resources, and new Play apps can no longer be submitted as APKs. `flutter build apk` alone yields a fat APK with all three ABIs, which is larger for everyone; `flutter build apk --split-per-abi` writes one APK per ABI and adds `ABI_VERSION * 1000` to each `versionCode` so they do not collide. So for a plant-care app's first Play release you upload the bundle, and keep split APKs for other stores, direct downloads or testers installing by hand.
code
bash · 9 lines# Upload to Google Play
flutter build appbundle
# build/app/outputs/bundle/release/app.aab
# Distribute outside Play, one APK per CPU family
flutter build apk --split-per-abi
# build/app/outputs/flutter-apk/app-armeabi-v7a-release.apk
# build/app/outputs/flutter-apk/app-arm64-v8a-release.apk
# build/app/outputs/flutter-apk/app-x86_64-release.apkgo deeper
Remember: flutter build appbundle for Google Play, flutter build apk for installing directly, and --split-per-abi to avoid one fat APK outside Play.
Explain what Play does with a bundle (per-device APKs), which three ABIs Flutter builds, and why split APKs get per-ABI version codes.
Choose artifacts per channel, test bundles with bundletool or internal tracks before release, and keep version codes consistent across bundles and APKs.
Decide which distribution channels the app supports and what each costs in build variants, signing and support for users outside Play.
## Two artifacts, two audiences A Flutter Android release can be packaged two ways: - An **Android App Bundle** (`.aab`) — a publishing format. You upload it; the store builds the installable APKs from it. - An **APK** (`.apk`) — the installable package itself. You can hand it to a device directly. The commands: ```bash flutter build appbundle # -> build/app/outputs/bundle/release/app.aab flutter build apk # -> one "fat" APK with every ABI flutter build apk --split-per-abi # -> one APK per ABI ``` `flutter build` defaults to **release** mode, so none of these needs `--release`. ## What goes inside A Flutter release build contains native code: the Flutter engine and your Dart code compiled ahead of time. Native code is per **ABI** (application binary interface, the CPU architecture family). Flutter's Android release builds target three ABIs: - `armeabi-v7a` (32-bit ARM), - `arm64-v8a` (64-bit ARM), - `x86_64` (64-bit x86, mostly emulators and some devices). By default the app bundle contains all three. ## Why the bundle for Google Play When you upload an `.aab`, Google Play generates **optimised APKs per device configuration**: a phone with an `arm64-v8a` CPU downloads only the `arm64-v8a` native libraries and the resources for its screen density and language. Users download less than they would with a fat APK. There is also a hard rule: Google Play no longer accepts **new apps** submitted as APKs, so the first release of a plant-care app has to be a bundle. Uploading a bundle also means enrolling in **Play App Signing**, where Google holds the key that signs the APKs it generates and you sign uploads with your own upload key. Before uploading, you can test a bundle offline with Google's `bundletool`, which generates the same device-specific APKs locally, or online through Play's internal testing tracks. ## When APKs still matter 1. **Stores or channels without bundle support** — other app stores, an enterprise MDM, a direct download from your website. 2. **Hand-installed test builds** — sending a build to a tester, or `flutter install` onto a connected device. 3. **Size-sensitive distribution outside Play** — here `--split-per-abi` matters. A plain `flutter build apk` produces a **fat APK**: one file with native code for all three ABIs. It installs anywhere but every user downloads code for CPUs they do not have. `flutter build apk --split-per-abi` instead writes: - `app-armeabi-v7a-release.apk` - `app-arm64-v8a-release.apk` - `app-x86_64-release.apk` ## The version-code detail Stores that accept several APKs for one app require each APK to have a **distinct version code**. When you split per ABI, Flutter therefore adds `ABI_VERSION * 1000` to the `versionCode` derived from `pubspec.yaml`. If you need the plain version code for every split, the Flutter docs describe forcing it with the `force-version-code-ignoring-abi=true` Gradle property. With an app bundle this does not arise: one bundle, one version code. | | `appbundle` | `apk` | `apk --split-per-abi` | |---|---|---|---| | Upload to Google Play (new app) | yes | no | no | | Install directly on a device | no (needs `bundletool`) | yes | yes, matching ABI | | Native code per file | all ABIs, split by Play | all ABIs | one ABI | | `versionCode` | from pubspec | from pubspec | pubspec + `ABI_VERSION * 1000` | ## Where the build lands Flutter writes Android release artifacts under `build/app/outputs/`: the bundle in `bundle/release/app.aab` and APKs in `flutter-apk/`. With product flavors the flavor name appears in the file name, for example `app-staging-release.apk`. CI jobs usually collect these paths as build artifacts; a wrong glob is a common reason a pipeline "succeeds" but uploads nothing. ## Things interviewers probe - **"Why is my APK so big?"** — it is a fat APK; users of a bundle-delivered app download far less. - **"Can I sideload the `.aab`?"** — not directly; an `.aab` is not installable without `bundletool` generating APKs from it. - **"Does the bundle change what my Dart code does?"** — no; it changes packaging and delivery, not the compiled program. - **Signing still applies** to both formats: an unsigned or debug-signed bundle is rejected on upload, which is the next thing to configure.
- Why does Flutter add ABI_VERSION * 1000 to the versionCode of split APKs?Stores that accept multiple APKs for one app require each APK to carry a distinct version code. Adding a per-ABI offset to the pubspec's build number makes the three split APKs unique; the Flutter docs describe a Gradle property to force the plain version code instead.
- How can you check what a device will actually receive from an app bundle before uploading it?Use Google's `bundletool` to generate the device-specific APK set from the `.aab` locally and install it on a device, or upload to an internal testing track and install from Play. Both exercise the same split that real users get.
saying these in an interview costs you the question
- Uploads a fat APK for a new Google Play app
- Thinks an .aab file can be installed directly on a phone
- Believes --split-per-abi is needed when uploading an app bundle
- Adds --release to flutter build, not knowing release is the default
- Thinks the bundle format changes how the Dart code runs