Audit Flutter Flavors and Entrypoints Before a Release Build
A release flavor can compile successfully while using the wrong app name, API environment, or bundle identity. Verify each configuration as a complete tuple.
A flavor is more than a Gradle task name
Teams often add --flavor staging and assume the app is configured. A real flavor usually spans a Dart entrypoint, Android product flavor, iOS scheme/configuration, application or bundle identifier, display name, and environment-specific services. One missing mapping can produce a build that is green but points at production.
Write down a table before debugging:
| Environment | Entrypoint | Android application ID | iOS bundle ID | API host |
|---|---|---|---|---|
| Staging | lib/main_staging.dart | com.example.app.staging | com.example.app.staging | staging host |
Run the exact release command locally, including flavor and target, and print a non-secret environment label at startup. Never print credentials to make configuration visible.
Validate both platform mappings
For Android, inspect the product flavor's application ID suffix, manifest placeholders, and signing configuration. For iOS, confirm the shared scheme is enabled for the expected archive action and that the build configuration selects the correct .xcconfig. A scheme existing in Xcode does not guarantee it is shared with CI.
Use a build matrix that names the complete tuple rather than constructing flags from a vague ENV string. Then test the installed artifact: check the displayed app name, bundle identifier, and a safe diagnostics screen showing the environment label. Compilation alone cannot verify that the runtime configuration is correct.
Keep service keys in the platform's approved secret mechanism and ensure staging credentials cannot access production data. When a flavor changes, update the mapping table, CI command, and smoke test together. This turns “which environment is this build?” from a guess into a release assertion.
Inspect the final configuration rather than trusting names
A flavor name is only one part of the effective build identity. Dart defines, native build flavors, application IDs, display names, and API endpoints can be configured in different files. Create a small matrix for each supported environment that records the Flutter entrypoint, Android flavor, iOS scheme or configuration, application identifier, and service endpoint. Then compare the matrix with the commands used by local developers and CI. If two environments share a bundle ID, installing them side by side may replace one app with the other even though the icons have different labels.
Use flavor-specific configuration for values that genuinely differ, and keep common defaults in one place. Do not treat --dart-define as secure secret storage: values bundled into a client application can be extracted. Public environment identifiers and feature endpoints may be reasonable defines; private API keys, signing credentials, and privileged tokens are not. Put secrets behind a trusted backend or inject them only into an appropriate server-side release process.
Verify the produced artifact for every platform
After a flavor build, inspect the artifact rather than stopping at a successful exit code. For Android, verify the application ID, version, signing mode, and endpoint configuration in the built variant. For iOS, inspect the archive's bundle identifier, entitlements, and embedded configuration. A build can succeed while selecting the wrong environment, which is more dangerous than a compile failure because it can produce a valid-looking artifact connected to the wrong service.
Make CI print the full non-secret command line and the resolved environment name. Add a smoke test that launches each debug or staging flavor and checks a safe environment marker in the UI or diagnostics endpoint. Keep release checks stricter: fail if a debug URL, test signing identity, or development-only permission setting appears in the production artifact. These checks catch configuration drift before distribution.
When a flavor-specific build fails, compare the failing target with the working target one dimension at a time. First confirm the entrypoint exists, then verify plugin registration, native scheme or variant names, and configuration file selection. Avoid creating many nearly identical flavor files without a clear ownership model. A compact matrix and one tested command per target give reviewers a much clearer signal than a collection of undocumented shell aliases.
Inspect the final configuration rather than trusting names
A flavor name is only one part of the effective build identity. Dart defines, native build flavors, application IDs, display names, and API endpoints can be configured in different files. Create a small matrix for each supported environment that records the Flutter entrypoint, Android flavor, iOS scheme or configuration, application identifier, and service endpoint. Then compare the matrix with the commands used by local developers and CI. If two environments share a bundle ID, installing them side by side may replace one app with the other even though the icons have different labels.
Use flavor-specific configuration for values that genuinely differ, and keep common defaults in one place. Do not treat --dart-define as secure secret storage: values bundled into a client application can be extracted. Public environment identifiers and feature endpoints may be reasonable defines; private API keys, signing credentials, and privileged tokens are not. Put secrets behind a trusted backend or inject them only into an appropriate server-side release process.
Verify the produced artifact for every platform
After a flavor build, inspect the artifact rather than stopping at a successful exit code. For Android, verify the application ID, version, signing mode, and endpoint configuration in the built variant. For iOS, inspect the archive's bundle identifier, entitlements, and embedded configuration. A build can succeed while selecting the wrong environment, which is more dangerous than a compile failure because it can produce a valid-looking artifact connected to the wrong service.
Make CI print the full non-secret command line and the resolved environment name. Add a smoke test that launches each debug or staging flavor and checks a safe environment marker in the UI or diagnostics endpoint. Keep release checks stricter: fail if a debug URL, test signing identity, or development-only permission setting appears in the production artifact. These checks catch configuration drift before distribution.
When a flavor-specific build fails, compare the failing target with the working target one dimension at a time. First confirm the entrypoint exists, then verify plugin registration, native scheme or variant names, and configuration file selection. Avoid creating many nearly identical flavor files without a clear ownership model. A compact matrix and one tested command per target give reviewers a much clearer signal than a collection of undocumented shell aliases.