Integrating Codemagic and Shorebird: A Flutter Developer’s Guide to Seamless CI/CD and Production Environments

MAHMOUD MOURAD
9 min readJun 23, 2024

--

Step 1: Connecting Your Flutter Project to Codemagic

After setting up your Codemagic account, the first crucial step is to link it with your Flutter project’s version control system (VCS) repository. This connection forms the foundation of your CI/CD pipeline, enabling Codemagic to access your code and trigger automated builds whenever changes are pushed.

  1. Add a New Application: In your Codemagic dashboard, click on “Add application” to initiate the process.
  2. Select Your Git Provider: Codemagic seamlessly integrates with popular Git providers like GitHub, GitLab, and Bitbucket. Choose the one where your Flutter project is hosted.
  3. Authenticate and Grant Access: You’ll be prompted to authenticate your Git account and authorize Codemagic to access your repositories.
  4. Choose Your Repository: From the list of available repositories, select the one containing your Flutter project.
  5. Configure Build Settings (Optional): Codemagic will attempt to auto-detect your project type and suggest build settings. You can review and customize these settings if needed (we’ll delve deeper into configuration later).
  6. Switch To YAML File Configs: Once you are on the workflow editor switch to the YAML Configuration since we need to add our custom script to build a release by Shorebird.

Step 2: Crafting Your CI/CD Workflow with codemagic.yaml

Now that your Flutter project is connected to Codemagic, it’s time to define how your CI/CD pipeline should operate. This is achieved through the codemagic.yaml configuration file. This file acts as the blueprint for Codemagic, outlining the steps to take whenever a change is pushed to your repository.

The Anatomy of codemagic.yaml

The codemagic.yaml file is written in YAML, a human-readable data serialization language. Let's break down its key components:

  • Workflows: A workflow represents a complete sequence of steps you want Codemagic to execute. You can define multiple workflows within your configuration file (e.g., one for building, one for testing, and one for deploying).
  • Steps: Steps are the individual actions that comprise a workflow. Each step performs a specific task, such as installing dependencies, running tests, or building your app.
  • Scripts: Within each step, you provide a script that specifies the commands to run. These commands are typically shell commands that interact with your Flutter project and its dependencies.

Prior to customizing your Codemagic workflow, let’s integrate Shorebird into your Flutter project. Start by installing Shorebird on your local development machine, referring to the installation guide: https://docs.shorebird.dev/#install. Once installed, create a Shorebird account through the sign-up process outlined here: https://docs.shorebird.dev/#sign-up.

Next, navigate to your project’s root directory in your terminal and execute the following command:

shorebird init

you will find that a shorebird.yaml file where included in your project, this file will include the app ID for your project along with flavor IDs if you have flavors setup for your project. in my case I have 6 flavors.

As our Codemagic configuration utilizes YAML, we need to provide access to certain files and credentials during the build process. This includes:

  • Android: Keystores for signing your Android app.
  • iOS: Provisioning profiles and distribution certificates for signing your iOS app.
  • Environment Variables:
  • Shorebird App ID and Token
  • Google Cloud Service Key (for publishing to Google Play Console)

To securely manage these files and secrets, navigate to the “Teams” section in Codemagic and upload them. Assign meaningful variable names to each file and credential, as these names will be referenced within your codemagic.yaml file during the build process.

To obtain your Shorebird token, open your terminal and execute the following command:

shorebird login:ci

This will pop a link into the terminal, and paste it into a browser window, prompting you to select and authorize your Google account. Upon successful authorization, your Shorebird token will be displayed directly in the terminal window.

take this token and add it to the global environment variable as shown in the previous image of global variables and secrets.

let’s get back to our application on code magic and add some build configs such as release version, dart-define arguments, build flavors.
you need to specify flutter version and export path for ios since it will cause errors if you don’t, see the picture below.

NOW NAVIGATE TO ‘codemagic.yaml’ FILE AND ADD THE FOLLOWING CODE:

definitions:
scripts:
- &flutter_analyze
name: Run Static Flutter Code Analysis
script: flutter analyze || true

- &shorebird_install
name: Install Shorebird CLI
script: |
# Install the Shorebird CLI
curl --proto '=https' --tlsv1.2 https://raw.githubusercontent.com/shorebirdtech/install/main/install.sh -sSf | bash
# Add Shorebird to PATH
export PATH="$HOME/.shorebird/bin:$PATH"
echo PATH="$HOME/.shorebird/bin:$PATH" >> $CM_ENV

- &build_android
name: Android Release Build
script: |
export SHOREBIRD_TOKEN="$SHOREBIRD_TOKEN" && shorebird release android --artifact=apk --flutter-version="$FLUTTER_VERSION" --flavor="$FLAVOR" --target="$TARGET_PATH" --dart-define="$RUNTIME_ARGS" -- --build-name="$CM_BUILD_ID" --build-number="$BUILD_NUMBER"

- &patch_android
name: Android PATCH Build
script: |
export SHOREBIRD_TOKEN="$SHOREBIRD_TOKEN" && shorebird patch android --flavor="$FLAVOR" --target="$TARGET_PATH" --dart-define="$RUNTIME_ARGS" --release-version="$RELEASE_VERSION"

- &set_up_keystore
name: Set-up Android Keystore
script: |
echo "flutter.sdk=$HOME/programs/flutter" > "$FCI_BUILD_DIR/android/local.properties"
cat >> "$CM_BUILD_DIR/android/key.properties" <<EOF
storePassword=$CM_KEYSTORE_PASSWORD
keyPassword=$CM_KEY_PASSWORD
keyAlias=$CM_KEY_ALIAS
storeFile=$CM_KEYSTORE_PATH
EOF

- &build_ios
name: iOS Release Build
script: |
export SHOREBIRD_TOKEN="$SHOREBIRD_TOKEN" && shorebird release ios --export-options-plist="$EXP_OPTIONS" --flutter-version="$FLUTTER_VERSION" --flavor="$FLAVOR" --target="$TARGET_PATH" --dart-define="$RUNTIME_ARGS" -- --build-name="$CM_BUILD_ID" --build-number="$BUILD_NUMBER"

- &patch_ios
name: iOS PATCH Build
script: |
export SHOREBIRD_TOKEN="$SHOREBIRD_TOKEN" && shorebird patch ios --export-options-plist="$EXP_OPTIONS" --flavor="$FLAVOR" --target="$TARGET_PATH" --dart-define="$RUNTIME_ARGS" --release-version="$RELEASE_VERSION"

- &use_provisioning_profiles
name: Apply Provisioning Profiles
script: |
xcode-project use-profiles
/usr/libexec/PlistBuddy -c 'Add :manageAppVersionAndBuildNumber bool false' /Users/builder/export_options.plist

workflows:
release-tr-production-android:
name: Release TR | Production | Android
instance_type: linux_x2
max_build_duration: 60
environment:
# The flutter version is stored in the CodeMagic Environment Variables
flutter: $FLUTTER_VERSION
java: 17
xcode: latest
cocoapods: default
android_signing:
# The keystore is stored in the CodeMagic Code signing identities
- tr_android_keystore
groups:
# the environment variable groups are stored in the CodeMagic Environment Variables
- google_play_credentials
- shorebird
- build-configs
vars:
TARGET: production
# The shorebird app id is stored in the CodeMagic Environment Variables
SHOREBIRD_APP_ID: $TR_SHOREBIRD_APP_ID
scripts:
- *set_up_keystore
- *flutter_analyze
- *shorebird_install
- *build_android
artifacts:
- build/**/outputs/*apk/**/*.apk
- build/**/outputs/*bundle/**/*.aab
- build/**/outputs/**/mapping.txt
publishing:
# To publish the app to Google Play, you need to have the service account credentials
google_play:
# The service account credentials are stored in the CodeMagic Environment Variables
credentials: $TR_GCLOUD_SERVICE_ACCOUNT_CREDENTIALS
# The track is internal, you can change it to alpha, beta or production depending on your needs,
# I recommend using internal for testing before releasing to production
track: internal

patch-tr-production-android:
name: Patch TR | Production | Android
instance_type: linux_x2
max_build_duration: 60
environment:
# The flutter version is stored in the CodeMagic Environment Variables
flutter: $FLUTTER_VERSION
java: 17
xcode: latest
cocoapods: default
android_signing:
# The keystore is stored in the CodeMagic Code signing identities
- tr_android_keystore
groups:
# the environment variable groups are stored in the CodeMagic Environment Variables
- google_play_credentials
- shorebird
- build-configs
vars:
TARGET: production
# The shorebird app id is stored in the CodeMagic Environment Variables
SHOREBIRD_APP_ID: $TR_SHOREBIRD_APP_ID
scripts:
- *set_up_keystore
- *flutter_analyze
- *shorebird_install
- *patch_android

release-tr-production-ios:
name: Release TR | Production | IOS
instance_type: mac_mini_m1
max_build_duration: 60
environment:
flutter: $FLUTTER_VERSION
java: 17
xcode: latest
cocoapods: default
ios_signing:
# The provisioning profile is stored in the CodeMagic Code signing identities
provisioning_profiles:
- profile: tr_app_store_provision_profile
# The certificate is stored in the CodeMagic Code signing identities
certificates:
- certificate: apple_dist_cert
groups:
- app_store_credentials
- shorebird
- build-configs
vars:
TARGET: production
# The shorebird app id is stored in the CodeMagic Environment Variables
SHOREBIRD_APP_ID: $TR_SHOREBIRD_APP_ID
integrations:
# the name of the key used to authenticate with the App Store Connect
app_store_connect: CodeMagicDeployment-Key
scripts:
- *flutter_analyze
- *shorebird_install
- *use_provisioning_profiles
- *build_ios
artifacts:
- build/ios/ipa/*.ipa
- build/**/outputs/**/Runner.app.dSYM
- $HOME/Library/Developer/Xcode/DerivedData/**/Build/**/Runner.app.dSYM
# The app is submitted to TestFlight
publishing:
app_store_connect:
auth: integration
submit_to_testflight: true

patch-tr-production-ios:
name: Patch TR | Production | IOS
instance_type: mac_mini_m1
max_build_duration: 60
environment:
flutter: $FLUTTER_VERSION
java: 17
xcode: latest
cocoapods: default
ios_signing:
# The provisioning profile is stored in the CodeMagic Code signing identities
provisioning_profiles:
- profile: tr_app_store_provision_profile
# The certificate is stored in the CodeMagic Code signing identities
certificates:
- certificate: apple_dist_cert
groups:
# the environment variable groups are stored in the CodeMagic Environment Variables
- app_store_credentials
- shorebird
- build-configs
vars:
TARGET: production
# The shorebird app id is stored in the CodeMagic Environment Variables
SHOREBIRD_APP_ID: $TR_SHOREBIRD_APP_ID
integrations:
# the name of the key used to authenticate with the App Store Connect
app_store_connect: $TR_APP_STORE_CONNECT_KEY
scripts:
- *flutter_analyze
- *shorebird_install
- *use_provisioning_profiles
- *patch_ios

Understanding Your codemagic.yaml Configuration

The codemagic.yaml file serves as the command center for your Codemagic build and release process. Let's break down its core components and how they work together:

Definitions (Scripts):

Think of these as reusable building blocks. The YAML script defines several scripts for tasks like static code analysis, setting up the Shorebird CLI, and building your app for Android or iOS. This keeps your configuration clean and efficient.

Workflows (Your CI/CD Pipelines):

  • Your project has four distinct workflows:
  • release-tr-production-android: Builds and releases a new version of your Android app for production.
  • patch-tr-production-android: Creates a patch (update) for your existing production Android app.
  • release-tr-production-ios: Builds and releases a new version of your iOS app for production.
  • patch-tr-production-ios: Creates a patch (update) for your existing production iOS app.

Build Environment:

  • This section specifies the exact versions of Flutter, Java, Xcode, and Cocoapods required for a successful build. This ensures consistency across your development environment and the Codemagic build machines.

Code Signing (Security):

  • Your Android and iOS apps need to be signed with digital certificates to ensure they’re from a trusted source. This part of the configuration handles the setup for those certificates using environment variables for security.

Environment Variables (Secrets):

  • Sensitive information like API keys, tokens, and credentials are stored as environment variables in Codemagic. This keeps them out of your code and configuration files for added security.

Build Artifacts (The Results):

  • This section tells Codemagic what files to save after the build. These include your APKs (Android app files), AABs (Google Play format), IPAs (iOS app files), and debugging symbols (for crash reporting).

Publishing:

  • Android: Configured to publish your Android app to the internal track on Google Play by default. You can easily change this to alpha, beta, or production.
  • iOS: Your iOS app is configured to be uploaded to TestFlight for testing. This is a crucial step before submitting to the App Store.

Important Note: Before this configuration works, you’ll need to:

  • Upload your code signing certificates and credentials to Codemagic.
  • Set up the required environment variables with your secret values.

This overview should help you understand how the YAML file orchestrates your entire build and release process with Codemagic and Shorebird!

AFTER BUILDING THE NEW RELEASE YOU SHOULD SEE THAT THE RELEASE HAS SUCCESSFULLY BEEN CREATED ON SHOREBIRD CONSOLE AND AFTER PATCHING A SPECIFIC RELEASE, INSTALLED PATCHES SHOULD APPEAR UNDER RELEASE INFO IN SHOREBIRD CONSOLE.

CHECK OUT THIS ARTICLE AS A WELL
https://medium.com/@alejandroferrero/foodium-ci-cd-with-codemagic-and-shorebird-in-flutter-apps-77a7870f8b84

--

--

MAHMOUD MOURAD
MAHMOUD MOURAD

No responses yet