Native Flavors, Automatic app_id Detection & Multi-Channel Matrix
In enterprise mobile engineering, a single Flutter codebase is frequently compiled into several build variants (Flavors: dev, staging, prod), deployed across two OS platforms (Android and iOS), and distributed over multiple rollout tracks (Channels: staging, beta, stable).
This page details how tk_ota_push resolves the application identifier (app_id) and handles the Flavor × Platform × Channel matrix.
1. 9-Level app_id Resolution Cascade
The FlutterProject.resolveEffectiveAppId(...) method (lib/src/cli/project.dart) determines the effective app_id following a strict precedence order from 1 (highest priority) down to 8/9 (automatic native inspection):
- Explicit CLI Flag:
--app-id <id>command option (highest priority). - Dart Define Flag: Compile-time variable
--dart-define=APP_ID=.... - Define Config File: Structured file
--dart-define-from-file. - Configuration File: Default
app_idset in rootshorebird.yaml.
Automatic Injection into shorebird.yaml (ensureShorebirdYamlAppId)
Even if you leave app_id blank in your source shorebird.yaml (Zero-Config paradigm), the CLI calls project.ensureShorebirdYamlAppId(effectiveAppId) before running compilation so that the native Rust engine embedded within the mobile package always finds the required app_id key in flutter_assets/shorebird.yaml.
2. Automatic Native Detection on Android and iOS (detectNativeAppId)
When no app_id is supplied via flag or shorebird.yaml, detectNativeAppId(platform, flavor, environment) directly inspects the project's native build scripts:
On Android (_detectAndroidApplicationId)
- Opens
android/app/build.gradle.kts(preferred) orandroid/app/build.gradle. - Reads the base
applicationId(or fallbacknamespace). - If a
flavororenvironmentis provided (e.g.,devorstaging), locates the matching block inproductFlavorsorbuildTypes(supporting Groovydev { ... }and Kotlin DSLcreate("dev") { ... }):- If the block defines an explicit
applicationId(e.g.,applicationId "com.enterprise.staging.custom"), uses that ID directly. - If the block defines an
applicationIdSuffix(e.g.,applicationIdSuffix ".dev"), appends it to baseapplicationId(e.g.,com.saramoney.retail.dev).
- If the block defines an explicit
- As a final fallback, reads
package="..."fromandroid/app/src/main/AndroidManifest.xml.
// Example android/app/build.gradle
android {
defaultConfig {
applicationId "com.saramoney.retail"
}
flavorDimensions "env"
productFlavors {
dev {
dimension "env"
applicationIdSuffix ".dev" // Inferred as: com.saramoney.retail.dev
}
staging {
dimension "env"
applicationId "com.saramoney.staging.custom" // Inferred as: com.saramoney.staging.custom
}
prod {
dimension "env" // Inferred as: com.saramoney.retail
}
}
}
On iOS (_detectIosBundleId)
- Opens
ios/Runner.xcodeproj/project.pbxproj. - Collects all
PRODUCT_BUNDLE_IDENTIFIERvalues, excluding test targets (RunnerTests,Tests) and unresolved variables ($(...)). - If a
flavoris provided, selects the identifier containing the flavor name (case-insensitive), otherwise selects the first valid identifier. - As a final fallback, reads
<key>CFBundleIdentifier</key>fromios/Runner/Info.plist.
3. Explicit Declarations in shorebird.yaml (environments & flavors)
If your backend application IDs differ from native package names, declare them in shorebird.yaml:
tenant_id: sara-money
base_url: https://ota.saramoney.internal
channel: stable
auto_update: false
patch_verification: strict
# Fallback default ID if no flavor/env is specified
app_id: com.saramoney.retail
# Priority 1 in shorebird.yaml
environments:
dev: sara-money-dev
staging: sara-money-staging
prod: sara-money-prod
# Priority 2 in shorebird.yaml
flavors:
dev: sara-money-dev
staging: sara-money-staging
prod: sara-money-prod
4. Understanding Flavor × Platform × Channel Combinations
Distinguish clearly between Flavor (applied at binary compilation), Platform, and Channel / Track (applied during patch rollout):
| Dimension | Action Layer | What it Isolates | Example |
|---|---|---|---|
Flavor (--flavor) | Binary Build (Gradle / Xcode) | Separate apps with distinct app_id, icons, API backends, and keystores. | dev (com.app.dev), prod (com.app) |
Platform (-p) & Arch (-a) | OS CPU binary format | Executable format specific to OS architecture. | android (aarch64, arm), ios (aarch64) |
Channel (-c, --channel / UpdateTrack) | OTA rollout within one app_id | Subset of users on the same Release receiving patches earlier. | staging (QA / Canary), beta, stable (Public) |
CLI Commands Combining Flavor, Platform, and Channel
# 1. Publish Android Production Release (AAB) on 'prod' flavor
tk-ota-push release -p android \
--flavor prod \
-t lib/main_prod.dart \
--dart-define-from-file=env/prod.json \
--artifact aab
# 2. Deploy a patch on 'prod' flavor, but target only the 'staging' channel (Canary QA)
tk-ota-push patch -p android \
--flavor prod \
-t lib/main_prod.dart \
--dart-define-from-file=env/prod.json \
--artifact aab \
--channel staging \
-m "QA validation on production binary"
# 3. Once validated by QA on 'staging', promote patch to 'stable'
tk-ota-push promote --patch-id <PATCH_UUID> --to-channel stable