Foodium — CI/CD with Codemagic and Shorebird in Flutter Apps

Alejandro Ferrero
13 min readApr 20, 2024

Learn how to configure Codemagic and Shorebird to automate releases and hotfixes for your Flutter App

In this second blog post of the Flutter Series about Foodium, I will cover the implementation of Codemagic workflows via a .yaml config file while integrating Shorebird in the building process for a Flutter App with multiple flavors: Production and Staging.

Usually, CI/CD configuration isn’t the first thing you put together when building a Flutter App, but it certainly is one of the most important things in the long run as it allows you to automate and delegate many tasks. So, let’s get into it!

Disclaimer: This application is not affiliated with codemagic.io product, Shorebird, or Flutter.

Foodium — Flutter, Codemagic and Shorebird

What’s what?

CI/CD

It stands for Continuous Integration and Continuous Deployment or Continuous Delivery. These are key concepts in modern software development practices that focus on making the process of integrating changes and deploying software more efficient, consistent, and reliable.

  • Continuous Integration (CI) refers to automating the integration of code changes from multiple contributors into a single software project. The goal is to provide quick feedback so that if a defect is introduced, it can be identified and corrected immediately. CI typically involves builds and test execution automation to ensure the new code integrates flawlessly within the existing code base.
  • Continuous Deployment (CD) extends CI by automatically deploying all code changes to a testing or production environment after the build stage. This approach allows developers to see how the application will perform in a live environment.
  • Continuous Delivery (CD) is similar to continuous deployment but with an additional emphasis on ensuring that the software can be released to production at any time. With continuous delivery, the deployment to a live production environment is done manually, providing an additional layer of control to ensure that deployments can be made reliably at any desired time.

These practices are part of a broader trend toward automation in software development, aiming to reduce manual labor, minimize errors, and increase the speed of software deliveries.

Codemagic

It’s a Continuous Integration and Continuous Deployment (CI/CD) platform specifically designed for mobile application projects. It supports building, testing, and deploying mobile app projects automatically. Here are some of its key features and aspects.

  • Platform Support: Codemagic is particularly known for its strong support for Flutter applications, but it also supports iOS, Android, React Native, Ionic, and other popular mobile development frameworks.
  • Automation: It automates building and testing processes for mobile apps, significantly speeding up development and release cycles. This includes, but is not limited to, running unit and widget tests, as well as analyzing the code.
  • Configuration: Codemagic allows configuration through a graphical user interface or by using codemagic.yaml configuration files, which can be version-controlled with your project's repository. This flexibility makes it easy to customize build workflows according to specific project needs.
  • Deployment: It supports deploying apps directly to app stores (Apple App Store and Google Play Store) or platforms like TestFlight for beta testing. It can also handle multiple deployment targets and configurations.
  • Integrations: Codemagic offers integration with popular tools and services, such as Slack for notifications, GitHub, GitLab, Bitbucket for source control, and more. This helps streamline various parts of the development pipeline.
  • Artifact Management: It handles the storage and management of build artifacts like APK and IPA files, making them easily accessible for testing or distribution.
  • Environment Variables and Secrets: Codemagic securely manages environment variables and secrets, enabling developers to store sensitive information like API keys or credentials.

Overall, Codemagic aims to simplify the CI/CD pipeline for mobile app developers, allowing them to focus more on development and less on the complexities of the build and release process.

Shorebird

Shorebird’s Code Push for Flutter is a tool designed to streamline and enhance the mobile app development process. Here’s an explanation focusing on its key features and benefits.

  • Code Push: Shorebird utilizes code push technology, allowing developers to push updates to their Flutter apps directly to users’ devices without going through the traditional app store review and update process. This is particularly useful for making live updates, fixing bugs, or adding minor features promptly. Apps built with Shorebird include a modified Flutter engine, which checks for updates to your app’s Dart code on startup. If an update is available, the engine downloads the update. The user will see the update on the next app restart after the update is downloaded.
  • Live Updates: With Shorebird, developers can deploy updates directly to their end-users in real time. This means that as soon as a new feature is developed or a bug is fixed, it can be immediately rolled out, improving the app’s responsiveness to user needs and issues.
  • Simplify Deployment: By integrating Shorebird into their Flutter applications, developers can bypass lengthy app store approval times, enabling quicker iteration cycles and more dynamic app management.
  • Additional Info: A more detailed walkthrough of their process is available in the Getting Started guide. The Concepts section contains more information about the terminology used in this guide.

Codemagic Setup

Before diving into the configuration of the codemagic.yaml file, you’ll need to have Codemagic properly set up. Here’s a list of mandatory steps you should follow before continuing to the next section — notice there’s no need to create the .yaml file just yet, so you can safely skip any yaml implementation/configuration instructions.

Shorebird Setup

Once again, you’ll need to do some preliminary work before we can address the codemagic.yaml file configuration. Here’s another list of mandatory steps — notice there’s no need to create the .yaml file just yet, so you can safely skip any yaml implementation/configuration instructions.

  • Getting Started with Shorebird
  • Codemagic with Shorebird — Make sure to keep theSHOREBIRD_TOKEN safely stored as it is a secret and should not be committed directly in your source code or shared publicly. However, do add it to Codemagic’s environment variables as instructed in the link.

codemagic.yaml Implementation

At this point, you should be all set to add the final configurations to have your Codemagic + Shorebird CI/CD implementation up and running. So, let’s finish it up!

Firstly, make sure your environment variables are properly set up.

  1. In https://codemagic.io/apps, click on “Teams” on the left-hand menu
  2. Click on “Personal Account” — or your team’s name if you have one.
  3. Expand the “Global variables and secrets” section by clicking on it.
  4. Make sure to add the following variables and their corresponding variables — some of these variables, if not all, you should have already added by following the Codemagic and Shorebird Setup sections above.
Codemagic Environment Variables

Make sure your Variable group names match the ones shown in the image. Otherwise, you’ll have to refer to your own custom names in the codemagic.yaml file.

Additionally, if you have any config file containing app-specific data, you can add its contents under the App environment variables.

  1. In https://codemagic.io/apps, click on the cog icon ⚙️ on your previously created app.
  2. Click on the “Environment variables” tab.
  3. Add the contents of your config file — Note that binaries need to be base64-encoded before they can be saved to environment variables.
App Environment Variables

In my case, I created a variable group named “foodium” and a variable named APP_CONFIG, which contains base64-encoded data. Notice how this data is additionally secured — make sure the “Secure” checkbox is checked when adding this variable as it may contain sensitive information. For more information on how to set this up, take a look at this tutorial from Codemagic: Environments (Flavors) in Flutter with Codemagic CI/CD.

Lastly, let’s look into the infamous codemagic.yaml file. I’ve added comments right into it so that you get the implementation along with a brief explanation all in one place.

# Use the definitions section to define reusable scripts that you can execute
# in any of your workflows below
definitions:
scripts:
# The App version is set here.
# This needs to be updated manually any time a new version is to be
# released to the stores.
- &set_version_number
name: 🔢 Set Version
script: |
echo "MAJOR_VERSION=2" >> $CM_ENV
echo "MINOR_VERSION=4" >> $CM_ENV
echo "PATCH_VERSION=0" >> $CM_ENV

- &set_up_local_properties
name: 🛠️ Set up local properties
script: |
# set up local properties
echo "flutter.sdk=$HOME/programs/flutter" > "$FCI_BUILD_DIR/android/local.properties"

- &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
# Set Shorebird PATH
echo PATH="/Users/builder/.shorebird/bin:$PATH" >> $CM_ENV

# This script grabs the previously defined environment variable APP_CONFIG
# and decodes it.
# Notice it also creates a /config directory at the root level and places
# an app_config.json file right under it, including the decoded contents.
# This is a Foodium-specific step as this App requires this file's contents
# at startup.
- &app_config
name: 🤐 App Configs
script: |
# Create directory if it doesn't exist
mkdir -p $CM_BUILD_DIR/config
# Write out the environment variable as a json file
echo $APP_CONFIG | base64 --decode > $CM_BUILD_DIR/config/app_config.json

- &flutter_analyze
name: 🔍 Run static code analysis
script: flutter analyze

- &use_provisioning_profiles
name: 🔑 Apply Provisioning Profiles
script: |
xcode-project use-profiles

# Use shorebird to create an Android release.
# Since Foodium uses flavors, we need to specify the desired flavor and target file.
# Additionally, the build-name and build-number arguments are passed after
# adding the necessary -- separator to distinguish between shorebird and flutter args.
- &build_android
name: 🤖 Android Release Build
script: |
shorebird release android --flavor ${FLAVOR} --target lib/main/main_${TARGET}.dart -- --build-name=$MAJOR_VERSION.$MINOR_VERSION.$PATCH_VERSION --build-number=$PROJECT_BUILD_NUMBER

# Use shorebird to create an iOS release.
# Since Foodium uses flavors, we need to specify the desired flavor and target file.
# Additionally, the build-name and build-number arguments are passed after
# adding the necessary -- separator to distinguish between shorebird and flutter args.
- &build_ios
name: 🍎 iOS Release Build
script: |
shorebird release ios --flavor ${FLAVOR} --target lib/main/main_${TARGET}.dart -- --export-options-plist /Users/builder/export_options.plist --build-name=$MAJOR_VERSION.$MINOR_VERSION.$PATCH_VERSION --build-number=$PROJECT_BUILD_NUMBER

# Use shorebird to create a patch for an existing android release.
# Since Foodium uses flavors, we need to specify the desired flavor and target file.
# In this case, the release-version argument is used to address the release
# version + build number this patch will apply to.
# Notice the version and build number need to be modified manually for every
# patch you send.
- &patch_android
name: 🤖 Android PATCH Build
script: |
shorebird patch android --flavor ${FLAVOR} --target lib/main/main_${TARGET}.dart --release-version 2.4.0+264

# Use shorebird to create a patch for an existing android release.
# Since Foodium uses flavors, we need to specify the desired flavor and target file.
# In this case, the release-version argument is used to address the release
# version + build number this patch will apply to.
# Notice the version and build number need to be modified manually for every
# patch you send.
- &patch_ios
name: 🍎 iOS PATCH Build
script: |
shorebird patch ios --flavor ${FLAVOR} --target lib/main/main_${TARGET}.dart --release-version 2.4.0+264

# The workflows section allows for the implementation of different workflows that'll
# get triggered/executed under different circumstances.
workflows:
# Since Foodium has different flavors, a production-specific workflow is defined.
release-foodium-production:
name: Release Foodium Production
# This integration allows for the publishing of this build to App Store Connect
integrations:
# AppStoreConnect_AuthKey is the name of the API key used to integrate
# Codemagic with Apple Developer Portal.
# Check this link for more info - https://docs.codemagic.io/flutter-code-signing/ios-code-signing/
app_store_connect: AppStoreConnect_AuthKey
environment:
# To perform iOS signing, a provisioning profile and certificate
# with the necessary permissions need to be provided.
# Notice the name of the profile and the certificate match the ones used
# when they were added to Codemagic.
# Check this link for more info - https://docs.codemagic.io/yaml-code-signing/signing-ios
ios_signing:
provisioning_profiles:
- prod_profile
certificates:
- dist_certificate
# Similarly, we need to sign the Android build by providing the corresponding
# keystore.
# Check this link for more info - https://docs.codemagic.io/flutter-code-signing/android-code-signing/
android_signing:
- production_keystore
# Since this is the production build for Foodium, the target and the flavor
# are both set to production.
# Add your app's unique bundle.
vars:
TARGET: production
FLAVOR: production
BUNDLE: your.app.bundle
# These group must have been previously defined on codemagic's dashboard.
# Notice foodium is an app-specific group, while the rest are account/team level
# groups which give us access to pre-defined environment variables.
groups:
- foodium
- app_store_credentials
- google_play_credentials
- certificate_credentials
- shorebird
flutter: 3.19.5
xcode: 15.0.1
cocoapods: default
# This version of java is required for Foodium's Android build.
# You may not need to set this argument.
java: 17
max_build_duration: 60
# This defines how and when this workflow will be triggered.
# Based on the parms passed below, the release-foodium-production workflow
# will be executed on a push to the main branch.
# # Check this link for more info - https://docs.codemagic.io/yaml-running-builds/starting-builds-automatically/
triggering:
events:
- push
branch_patterns:
- pattern: 'main'
cancel_previous_builds: true
# The scripts that will be run and their execution order is defined here.
scripts:
- *set_version_number
- *set_up_local_properties
- *shorebird_install
- *app_config
- *build_android
- *use_provisioning_profiles
- *build_ios
# These are the resulting files/binaries produced by the building process.
artifacts:
- build/**/outputs/*apk/**/*.apk
- build/**/outputs/*bundle/**/*.aab
- build/**/outputs/**/mapping.txt
- build/ios/ipa/*.ipa
- /tmp/xcodebuild_logs/*.log
# Publishing to the App Store and Play Store requires the corresponding
# authentication credentials.
# For iOS, the previously-configured integration method is used, while for
# android, we pass the contents of the GCLOUD_SERVICE_ACCOUNT_CREDENTIALS variable
# defined in the environment variables.
# In this case, we're automatically submitting the builds to Testflight for iOS
# and the internal test track for Andriod -- Configure the submission to your needs.
publishing:
app_store_connect:
auth: integration
submit_to_testflight: true
submit_to_app_store: false
google_play:
credentials: $GCLOUD_SERVICE_ACCOUNT_CREDENTIALS
track: internal
# Configure email notifications about the store publishing result.
email:
recipients:
- someone@email.com
notify:
success: true
failure: false

# Since Foodium has different flavors, a staging-specific workflow is defined.
release-foodium-staging:
name: Release Foodium Staging
# This integration allows for the publishing of this build to App Store Connect
integrations:
# AppStoreConnect_AuthKey is the name of the API key used to integrate
# Codemagic with Apple Developer Portal.
# Check this link for more info - https://docs.codemagic.io/flutter-code-signing/ios-code-signing/
app_store_connect: AppStoreConnect_AuthKey
environment:
# To perform iOS signing, a provisioning profile and certificate
# with the necessary permissions need to be provided.
# Notice the name of the profile and the certificate match the ones used
# when they were added to Codemagic.
# Check this link for more info - https://docs.codemagic.io/yaml-code-signing/signing-ios
ios_signing:
provisioning_profiles:
- stg_profile
certificates:
- dist_certificate
# Similarly, we need to sign the Android build by providing the corresponding
# keystore.
# Check this link for more info - https://docs.codemagic.io/flutter-code-signing/android-code-signing/
android_signing:
- staging_keystore
# Since this is the staging build for Foodium, the target and the flavor
# are both set to staging.
# Add your app's unique bundle.
vars:
TARGET: production
FLAVOR: production
BUNDLE: your.app.bundle.stg
# These group must have been previously defined on codemagic's dashboard.
# Notice foodium is an app-specific group, while the rest are account/team level
# groups which give us access to pre-defined environment variables.
groups:
- foodium
- app_store_credentials
- google_play_credentials
- certificate_credentials
- shorebird
flutter: 3.19.5
xcode: 15.0.1
cocoapods: default
# This version of java is required for Foodium's Android build.
# You may not need to set this argument.
java: 17
max_build_duration: 60
# This defines how and when this workflow will be triggered.
# Based on the parms passed below, the release-foodium-staging workflow
# will be executed on a push to the staging branch.
# # Check this link for more info - https://docs.codemagic.io/yaml-running-builds/starting-builds-automatically/
triggering:
events:
- push
branch_patterns:
- pattern: 'staging'
cancel_previous_builds: true
# The scripts that will be run and their execution order is defined here.
scripts:
- *set_version_number
- *set_up_local_properties
- *shorebird_install
- *app_config
- *build_android
- *use_provisioning_profiles
- *build_ios
# These are the resulting files/binaries produced by the building process.
artifacts:
- build/**/outputs/*apk/**/*.apk
- build/**/outputs/*bundle/**/*.aab
- build/**/outputs/**/mapping.txt
- build/ios/ipa/*.ipa
- /tmp/xcodebuild_logs/*.log
# Publishing to the App Store and Play Store requires the corresponding
# authentication credentials.
# For iOS, the previously-configured integration method is used, while for
# android, we pass the contents of the GCLOUD_SERVICE_ACCOUNT_CREDENTIALS variable
# defined in the environment variables.
# In this case, we're automatically submitting the builds to Testflight for iOS
# and the internal test track for Andriod -- Configure the submission to your needs.
publishing:
app_store_connect:
auth: integration
submit_to_testflight: true
submit_to_app_store: false
google_play:
credentials: $GCLOUD_SERVICE_ACCOUNT_CREDENTIALS
track: internal
# Configure email notifications about the store publishing result.
email:
recipients:
- someone@email.com
notify:
success: true
failure: false

# We're only interested in patching production releases.
# Therefore, a single workflow targetting a previously released production
# build is necessary.
# Notice only differences with respect to the two previous workflows are commented.
patch-foodium-production:
name: Patch Foodium Production
integrations:
app_store_connect: AppStoreConnect_AuthKey
max_build_duration: 60
triggering:
events:
- push
branch_patterns:
# In this case, we're triggering this build when pushing to the path branch.
- pattern: 'patch'
cancel_previous_builds: true
environment:
java: 17
groups:
- foodium
- app_store_credentials
- google_play_credentials
- certificate_credentials
- shorebird
flutter: 3.19.5
xcode: 15.0.1
cocoapods: default
vars:
TARGET: production
FLAVOR: production
BUNDLE: your.app.bundle
android_signing:
- production_keystore
ios_signing:
provisioning_profiles:
- prod_profile
certificates:
- dist_certificate
scripts:
- *set_version_number
- *set_up_local_properties
- *shorebird_install
- *app_config
# Make sure to add the corresponding Android and iOS path scripts.
- *patch_android
- *patch_ios
# Once the patches are finished, builds for Play Store Console and
# App Store Connect are created.
# This allows us to have a release version incluiding the "patched" code
# and have it released later on once it's approved by the given stores reviewers.
- *build_android
- *use_provisioning_profiles
- *build_ios
artifacts:
- build/**/outputs/*apk/**/*.apk
- build/**/outputs/*bundle/**/*.aab
- build/**/outputs/**/mapping.txt
- build/ios/ipa/*.ipa
- /tmp/xcodebuild_logs/*.log
publishing:
app_store_connect:
auth: integration
submit_to_testflight: true
submit_to_app_store: false
google_play:
credentials: $GCLOUD_SERVICE_ACCOUNT_CREDENTIALS
track: internal
email:
recipients:
- someone@email.com
notify:
success: true
failure: false

Here are a few additional notes to take into account when configuring your codemagic.yaml file.

Foodium Take

Being able to streamline Foodium’s CI/CD iterate over new functionalities and fix bugs much faster while releasing better and more stable versions more confidently. Additionally, the integration of Shorebird in this process takes the ability to address critical bugs to a whole other level, as we’re able to apply hotfixes by code push patches in a timely and effective manner.

Lastly, if you’re an avid foodie and love reviewing the places you eat or drink at, you may want to give Foodium a try.

And, as always, feel free to reach out in the comment section or on social media to share any feedback you may have, especially if it’s food-related 🤤

--

--

Alejandro Ferrero

🤤 Founder of Foodium 💙 Lead Mobile Developer at iVisa 💻 Former Google DSC Lead at PoliMi 🔗 Blockchain enthusiast 🇪🇸 🇺🇸 🇮🇹 🇦🇩 Been there, done that