Skip to content

Apps with flavors

If your app has flavors (Android product flavors, or Xcode schemes on iOS and macOS), each flavor gets its own Shorebird app, with its own releases and patches. You pass --flavor to every shorebird release and shorebird patch, plus --target when the flavor has its own entry point.

Flavors work on Android, iOS and macOS. On Windows and Linux, shorebird release and shorebird patch don’t take --flavor: passing it stops with Flavors are not supported on this platform., and without it they use app_id.

This page uses two flavors, development and production. To test a patch before your users get it, you don’t need a separate flavor: publish it to a staging track and promote it once it works. If you use a flavor per customer brand, see White Labeling.

Add the flavors to android/app/build.gradle.kts (or build.gradle):

android {
    // ...
    flavorDimensions += "environment"
    productFlavors {
        create("development") {
            dimension = "environment"
            applicationIdSuffix = ".dev"
            manifestPlaceholders["applicationLabel"] = "[Dev] My App"
        }
        create("production") {
            dimension = "environment"
            manifestPlaceholders["applicationLabel"] = "My App"
        }
    }
}

applicationIdSuffix gives each flavor its own package name, so both can be installed side by side. Leave it out if a service you use, such as Firebase, expects one package name. To show each flavor’s label, set android:label="${applicationLabel}" on the <application> element in android/app/src/main/AndroidManifest.xml. Flutter’s Android flavors guide covers the rest.

Create an Xcode scheme for each flavor, with build configurations for it, as Flutter’s iOS and macOS flavors guide describes. Each flavor needs an Xcode scheme of the same name. Shorebird 1.6.135 and newer check this before building, and stop with an error like this one if a scheme is missing:

The iOS project has no Xcode scheme for flavor "development", so it cannot be built with --flavor=development. Add a scheme named "development" (see https://docs.shorebird.dev/code-push/guides/flavors/ios/), or pass one of: production.

Use the same flavor names on every platform. Shorebird keeps one list of flavors for the project, so a flavor named production on Android and iOS is one Shorebird app with an Android and an iOS release, while prod on Android and production on iOS would be two apps.

Run shorebird init in your project:

shorebird init

It detects your flavors, creates a Shorebird app for each one, and lists them under flavors: in shorebird.yaml. What app_id holds depends on the project:

  • In a new project where Android and iOS both have flavors, app_id is the first flavor’s app.
  • In a new project where Android or iOS has no flavors, app_id is a separate app for builds without a flavor.
  • In a project that already has a shorebird.yaml, shorebird init adds an app for each flavor that isn’t listed yet and keeps your existing app_id.

For an existing project, shorebird.yaml then looks something like:

app_id: 6d4f1a2b-9c3e-4b7a-8f21-3e5d7c9a0b14
flavors:
  development: ee322dc4-3dc2-4324-90a9-04c40a62ae76
  production: 904bd3d5-3526-4c1c-a832-7ac23c95302d

A build with --flavor checks for patches with that flavor’s app ID. A build without --flavor uses app_id, and shorebird release and shorebird patch warn about it:

The project has flavors development, production, but no --flavor argument was provided.
The default app id 6d4f1a2b-9c3e-4b7a-8f21-3e5d7c9a0b14 will be used. Pass --flavor=<name> to target a flavor.

To add a flavor later, set it up as above and run shorebird init again. It prints New flavors detected: with the new flavor’s name and adds it to shorebird.yaml.

Release each flavor you ship, with its --flavor. If the flavor has its own entry point, pass it with --target; without it, Flutter builds lib/main.dart.

shorebird release android --flavor production --target lib/main_production.dart
shorebird release ios --flavor production --target lib/main_production.dart

Upload each flavor’s build to that flavor’s store listing. On Android, the app bundle is at build/app/outputs/bundle/productionRelease/app-production-release.aab; on iOS, the IPA is in build/ios/ipa.

To try a release on a device, pass the flavor to shorebird preview:

shorebird preview --flavor development --release-version 1.0.0+1

Build the patch with the same --flavor, --target, --dart-define and --dart-define-from-file values as the release it patches:

shorebird patch android --flavor production --target lib/main_production.dart \
  --release-version 1.0.0+1 --track=stable

--flavor picks which Shorebird app gets the patch. --target and the Dart defines are compiled into your Dart code, so a patch built without them runs a different entry point or configuration from the release.

Each flavor is its own app, so a fix that every flavor needs is a patch for each flavor, built from the same code. To check a patch before users get it, publish it to a staging track and promote it; see Staging patches.

In CI, run the release or patch once per flavor. A shell loop works on any CI system that has SHOREBIRD_TOKEN set (see the CI guides):

#!/usr/bin/env bash
set -euo pipefail
for flavor in development production; do
  shorebird patch android --flavor "$flavor" --target "lib/main_$flavor.dart" \
    --release-version=latest --track=stable
done

--release-version=latest patches each flavor’s most recently updated release. If a newer release might still be in store review, pass the version your users have instead; see Patching.

In GitHub Actions, a matrix runs a job per flavor, and the action’s args input passes the flavor and target:

jobs:
  patch:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        flavor: [development, production]
    env:
      SHOREBIRD_TOKEN: ${{ secrets.SHOREBIRD_TOKEN }}
    steps:
      - uses: actions/checkout@v7
      - uses: shorebirdtech/setup-shorebird@v1
        with:
          cache: true
      - uses: actions/setup-java@v6
        with:
          distribution: 'temurin'
          java-version: '17'
      - uses: shorebirdtech/shorebird-patch@v1
        with:
          platform: android
          release-version: latest
          track: stable
          args:
            --flavor ${{ matrix.flavor }} --target lib/main_${{ matrix.flavor
            }}.dart

See GitHub Integration for the action’s inputs.