{"id":31914,"date":"2022-01-19T15:38:53","date_gmt":"2022-01-19T23:38:53","guid":{"rendered":"https:\/\/clickup.com\/blog\/?p=31914"},"modified":"2026-02-18T08:05:56","modified_gmt":"2026-02-18T16:05:56","slug":"flutter-ci-cd","status":"publish","type":"post","link":"https:\/\/clickup.com\/blog\/flutter-ci-cd\/","title":{"rendered":"Flutter CI &#038; CD at ClickUp"},"content":{"rendered":"\n<p>With a growing mobile team and weekly releases, it&#8217;s essential to have a reliable CI pipeline. We need to be able to build and test our apps and deploy them to the App Store and Google Play Store. We also maintain internal preview versions to test the latest features.<\/p>\n\n\n\n<p>Learn how we use <a href=\"https:\/\/clickup.com\">ClickUp<\/a>, <a href=\"https:\/\/fastlane.tools\" rel=\"noreferrer noopener\" target=\"_blank\">Fastlane<\/a>, and <a href=\"https:\/\/github.com\/features\/actions\" rel=\"noreferrer noopener\" target=\"_blank\">GitHub Actions<\/a> to automate our Continuous Integration (CI) and Continuous Delivery (CD).<\/p>\n\n\n<div class=\"wp-block-ub-table-of-contents-block ub_table-of-contents\" id=\"ub_table-of-contents-eca61da9-d916-43ee-b936-c345fd85a83c\" data-linktodivider=\"false\" data-showtext=\"show\" data-hidetext=\"hide\" data-scrolltype=\"auto\" data-enablesmoothscroll=\"false\" data-initiallyhideonmobile=\"false\" data-initiallyshow=\"true\"><div class=\"ub_table-of-contents-header-container\" style=\"\">\n\t\t\t<div class=\"ub_table-of-contents-header\" style=\"text-align: left; \">\n\t\t\t\t<div class=\"ub_table-of-contents-title\">This Article Contains:<\/div>\n\t\t\t\t\n\t\t\t<\/div>\n\t\t<\/div><div class=\"ub_table-of-contents-extra-container\" style=\"\">\n\t\t\t<div class=\"ub_table-of-contents-container ub_table-of-contents-1-column \">\n\t\t\t\t<ul style=\"\"><li style=\"\"><a href=\"https:\/\/clickup.com\/blog\/flutter-ci-cd\/#0-the-life-of-a-bug-\" style=\"\">The life of a bug \ud83d\udc1c<\/a><\/li><li style=\"\"><a href=\"https:\/\/clickup.com\/blog\/flutter-ci-cd\/#1-pr-workflow-\" style=\"\">PR workflow \ud83d\udcdc<\/a><\/li><li style=\"\"><a href=\"https:\/\/clickup.com\/blog\/flutter-ci-cd\/#3-web-preview-\" style=\"\">Web Preview \ud83d\udd78<\/a><\/li><li style=\"\"><a href=\"https:\/\/clickup.com\/blog\/flutter-ci-cd\/#5-fastlane-\" style=\"\">Fastlane \ud83d\udca8<\/a><\/li><li style=\"\"><a href=\"https:\/\/clickup.com\/blog\/flutter-ci-cd\/#8-release-amp-prod-workflow-\" style=\"\">Release &amp; prod workflow \ud83d\ude80<\/a><\/li><li style=\"\"><a href=\"https:\/\/clickup.com\/blog\/flutter-ci-cd\/#9-more-tricks-for-your-ci-\" style=\"\">More tricks for your CI \ud83e\uddbe<\/a><\/li><\/ul>\n\t\t\t<\/div>\n\t\t<\/div><\/div>\n\n\n\n\n<h2 class=\"wp-block-heading\" id=\"0-the-life-of-a-bug-\">The life of a bug \ud83d\udc1c<\/h2>\n\n\n\n<p>Let&#8217;s start by quickly going over our process of managing and fixing bugs.<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>A bug (or feature request) gets reported and a task is created in ClickUp<\/li>\n\n\n\n<li>The task is assigned to a developer and fixed in a PR against the staging branch<\/li>\n\n\n\n<li>The CI runs all tests, builds the app, deploys a web preview, and uploads everything to the ClickUp task<\/li>\n\n\n\n<li>Our QA team verifies the fix and if it is good, the task is marked as <strong>done<\/strong><\/li>\n\n\n\n<li>The PR gets merged into staging automatically<\/li>\n\n\n\n<li>The staging branch gets built and deployed to our internal TestFlight<\/li>\n\n\n\n<li>Every Wednesday a release branch is created, built, and tested<\/li>\n\n\n\n<li>On Fridays, we create a release on GitHub and the CI deploys the release to the App Store and Play Store.<\/li>\n<\/ol>\n\n\n\n<p>A ClickUp task contains everything about the bug. We use <a href=\"https:\/\/clickup.com\/features\/custom-task-statuses\">Custom Statuses<\/a> like In Progress or Code Review to keep track of the bug. The CI workflows change the status automatically. <a href=\"https:\/\/clickup.com\/features\/custom-fields\">Custom Fields<\/a> contain additional information like who reported the bug, who works on it when it will be released etc.<\/p>\n\n\n\n\n\n<h2 class=\"wp-block-heading\" id=\"1-pr-workflow-\">PR workflow \ud83d\udcdc<\/h2>\n\n\n\n<p>The first two steps outlined above are not really CI-related, but the third one is interesting&#8230;<\/p>\n\n\n\n<p>Our <code>development<\/code> workflow runs for any PR. It checks lints, formatting and runs all tests before it starts building the Android and iOS artifacts.<\/p>\n\n\n\n<pre class=\"syntax\" data-enlighter-language=\"yaml\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">name: development\non: pull_request\njobs:\n  cancel:\n    name: 'Cancel Previous Runs'\n    runs-on: ubuntu-latest\n    timeout-minutes: 3\n    steps:\n      - uses: styfle\/cancel-workflow-action@0.9.1\n        with:\n          access_token: ${{ github.token }}\n  check:\n    name: Check formatting\n    runs-on: ubuntu-latest\n    needs: cancel\n    timeout-minutes: 5\n    steps:\n      - uses: actions\/checkout@v2\n      - name: Setup Flutter\n        uses: subosito\/flutter-action@v1\n      - name: Install dependencies\n        run: flutter pub get\n      - name: Check formatting\n        run: flutter format --dry-run . --set-exit-if-changed\n      - name: Check lints\n        run: flutter analyze\n      - name: Run tests\n        run: flutter test\n# build, post task message etc.<\/pre>\n\n\n\n<p>When a build is successful, the CI will post a message in the linked task. QA Engineers can go to the PR, download the build artifacts or use the web preview.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1400\" height=\"801\" src=\"https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/01\/bot-message-1400x801.png\" alt=\"The CI will post a message in the linked task\" class=\"wp-image-31921\" title=\"\" srcset=\"https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/01\/bot-message-1400x801.png 1400w, https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/01\/bot-message-300x172.png 300w, https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/01\/bot-message-768x439.png 768w, https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/01\/bot-message-1536x879.png 1536w, https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/01\/bot-message-700x400.png 700w, https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/01\/bot-message.png 1720w\" sizes=\"auto, (max-width: 1400px) 100vw, 1400px\" \/><figcaption class=\"wp-element-caption\"><p style=\"text-align:center\">An automated CI message posted in the linked ClickUp task after a successful build<\/p><\/figcaption><\/figure><\/div>\n\n\n<h3 class=\"wp-block-heading\" id=\"2-setting-up-flutter-on-the-ci-runner-\">Setting up Flutter on the CI runner \ud83d\udee0<\/h3>\n\n\n\n<p>We use the well-known GitHub action <a href=\"https:\/\/github.com\/marketplace\/actions\/flutter-action\" rel=\"noreferrer noopener\" target=\"_blank\">subosito\/flutter-action<\/a> to set up Flutter on the CI. By default, it will install the latest stable Flutter release. To avoid breaking your CI workflows when a new Flutter version is released, you should specify the version manually.<\/p>\n\n\n\n<pre class=\"syntax\" data-enlighter-language=\"yaml\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">steps:\n- uses: actions\/checkout@v2\n- uses: subosito\/flutter-action@v1\n  with:\n    flutter-version: 2.8.1<\/pre>\n\n\n\n<p>If you have multiple workflows, it is better to store the flutter version in a file. We use <code>FLUTTER_VERSION<\/code> in the root of the repository.<\/p>\n\n\n\n<pre class=\"syntax\" data-enlighter-language=\"yaml\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">steps:\n- uses: actions\/checkout@v2\n- name: Read FLUTTER_VERSION\n  id: get-version\n  run: echo \"::set-output name=version::$(cat FLUTTER_VERSION)\"\n- uses: subosito\/flutter-action@v1\n  with:\n    flutter-version: ${{ steps.get-version.outputs.version }}<\/pre>\n\n\n\n<p>Another easy solution is to store the Flutter version as GitHub secret and access it using <code>{{ secrets.FLUTER_VERSION }}<\/code>.<\/p>\n\n\n\n\n\n<h2 class=\"wp-block-heading\" id=\"3-web-preview-\">Web Preview \ud83d\udd78<\/h2>\n\n\n\n<p>Thanks to Flutter&#8217;s ability to run on the web, we can create a fully functional web preview of pull requests. Using the <a href=\"https:\/\/pub.dev\/packages\/device_preview\" rel=\"noreferrer noopener\" target=\"_blank\">device_preview<\/a> package, the device size and settings can be adjusted.<\/p>\n\n\n\n<p>The preview has proven to be very useful and is not only used by our QA team. Designers and Product managers also like it to quickly iterate on new features.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1400\" height=\"1346\" src=\"https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/01\/CleanShot-2022-01-20-at-19.40.42@2x-1400x1346.png\" alt=\"Fully functional web preview in Flutter\" class=\"wp-image-31965\" srcset=\"https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/01\/CleanShot-2022-01-20-at-19.40.42@2x-1400x1346.png 1400w, https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/01\/CleanShot-2022-01-20-at-19.40.42@2x-300x288.png 300w, https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/01\/CleanShot-2022-01-20-at-19.40.42@2x-768x738.png 768w, https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/01\/CleanShot-2022-01-20-at-19.40.42@2x-1536x1476.png 1536w, https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/01\/CleanShot-2022-01-20-at-19.40.42@2x-700x673.png 700w, https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/01\/CleanShot-2022-01-20-at-19.40.42@2x.png 1596w\" sizes=\"auto, (max-width: 1400px) 100vw, 1400px\" \/><figcaption class=\"wp-element-caption\"><p style=\"text-align:center\">via Flutter<\/p><\/figcaption><\/figure><\/div>\n\n\n<h3 class=\"wp-block-heading\" id=\"4-how-to-create-a-web-preview-\">How to create a web preview \ud83d\udc36<\/h3>\n\n\n\n<p>To get started, make sure that your app is <a href=\"https:\/\/docs.flutter.dev\/get-started\/web\" rel=\"noreferrer noopener\" target=\"_blank\">compatible<\/a> with Flutter web\u2014not all APIs are supported. <\/p>\n\n\n\n<p>In our app, for example, we needed to disable push notifications and web sockets.<\/p>\n\n\n\n<p>This sample workflow builds a web preview of your Flutter app and uploads it to an S3 bucket. We use an <code>ENABLE_DEVICE_PREVIEW<\/code> environment variable to disable the <code>device_preview<\/code> in production.<\/p>\n\n\n\n<pre class=\"syntax\" data-enlighter-language=\"yaml\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">build-preview:\n  name: Build preview\n  runs-on: ubuntu-latest\n  env:\n    AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}\n    AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}\n  steps:\n    - uses: actions\/checkout@v2\n    - name: Setup Flutter\n      uses: subosito\/flutter-action@v1\n    - name: Install dependencies\n      run: flutter pub get\n    - name: Build web\n      run: flutter build web --release --dart-define ENABLE_DEVICE_PREVIEW=true\n    - name: Fix base href\n      uses: jacobtomlinson\/gha-find-replace@0.1.4\n      with:\n        find: '&lt;base href=\"\/\">'\n        replace: '&lt;base href=\"\/flutter-preview-${{ github.head_ref }}\/\">'\n        include: build\/web\/index.html\n    - name: Deploy to S3\n      uses: reggionick\/s3-deploy@v3\n      with:\n        folder: build\/web\n        bucket: yourbucket\/flutter-preview-${{ github.head_ref }}\n        bucket-region: us-west-2\n        no-cache: true<\/pre>\n\n\n\n<p>The <code>Fix base href<\/code> step is needed because the preview will not be at the root of the bucket.<\/p>\n\n\n\n<p>And some code to conditionally enable the <code>device_preview<\/code>.<\/p>\n\n\n\n<pre class=\"syntax\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">import 'package:device_preview\/device_preview.dart';\nconst _preview = bool.fromEnvironment('ENABLE_DEVICE_PREVIEW', defaultValue: false);\nvoid main() => runApp(\n  DevicePreview(\n    enabled: _preview,\n    builder: (context) => MyApp(),\n  ),\n)<\/pre>\n\n\n\n<p>Environment variables are a powerful tool and allow Flutter&#8217;s tree shaking algorithm to drop debug code for release builds.<\/p>\n\n\n\n\n\n<h2 class=\"wp-block-heading\" id=\"5-fastlane-\">Fastlane \ud83d\udca8<\/h2>\n\n\n\n<p>Fastlane greatly simplifies building, signing, and deploying Flutter apps. It manages our certificates, provisioning profiles, and other settings. We use GitHub secrets to store passwords and tokens securely.<\/p>\n\n\n\n<p>Useful Fastlane actions:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/docs.fastlane.tools\/actions\/match\/\" target=\"_blank\" rel=\"noreferrer noopener\">fastlane match<\/a> to create and store iOS keys and profiles in a GitHub repository<\/li>\n\n\n\n<li><a href=\"https:\/\/docs.fastlane.tools\/actions\/build_app\/\" target=\"_blank\" rel=\"noreferrer noopener\">build_app<\/a> to build iOS and Android apps<\/li>\n\n\n\n<li><a href=\"https:\/\/docs.fastlane.tools\/actions\/upload_to_testflight\/\" target=\"_blank\" rel=\"noreferrer noopener\">upload_to_testflight<\/a> and <a href=\"https:\/\/docs.fastlane.tools\/actions\/deliver\/\" target=\"_blank\" rel=\"noreferrer noopener\">deliver<\/a> to deploy iOS builds<\/li>\n\n\n\n<li><a href=\"https:\/\/docs.fastlane.tools\/actions\/upload_to_play_store\/\" target=\"_blank\" rel=\"noreferrer noopener\">upload_to_play_store<\/a> to deploy Android builds<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"6-sample-ios-dev-build-\">Sample iOS dev build \ud83c\udf4f<\/h3>\n\n\n\n<pre class=\"syntax\" data-enlighter-language=\"ruby\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">platform :ios do\n  desc \"Create a dev build\"\n  lane :build_dev do\n    setup_ci\n    api_key = app_store_connect_api_key(\n      key_id: ENV[\"IOS_KEY_ID\"],\n      issuer_id: ENV[\"IOS_ISSUER_ID\"],\n      key_content: ENV[\"IOS_APP_STORE_API_KEY\"],\n    )\n    match(\n      type: \"development\",\n      app_identifier: [\"com.example.mobile\"],\n      api_key: api_key,\n      git_basic_authorization: Base64.strict_encode64(ENV[\"GH_USER_TOKEN\"])\n    )\n    build_app(\n      workspace: \"Runner.xcworkspace\",\n      scheme: \"Runner\",\n      export_method: \"development\",\n      export_options: {\n        method: \"development\",\n        compileBitcode: false,\n        provisioningProfiles: { \n          \"com.example.mobile\" => \"match Development com.example.mobile\"\n        }\n      }\n    )\n  end\nend<\/pre>\n\n\n\n<p>Don&#8217;t forget <code>setup_ci<\/code> it will save you from weird errors \ud83d\udc7e. Learn more about <a href=\"https:\/\/docs.flutter.dev\/deployment\/cd\" rel=\"noreferrer noopener\" target=\"_blank\">Fastlane for Flutter apps<\/a>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"7-android-signing-\">Android signing \ud83d\udd12<\/h3>\n\n\n\n<p>The easiest way to sign Android release builds securely is to store the tokens as GitHub secrets and use environment variables and a temporary <code>key.jks<\/code> created by the CI:<\/p>\n\n\n\n<pre class=\"syntax\" data-enlighter-language=\"kotlin\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">android {\n  \/\/ ...\n  signingConfigs {\n      release {\n          storeFile file('key.jks')\n          storePassword System.env['ANDROID_STORE_PASSWORD']\n          keyAlias System.env['ANDROID_KEY_ALIAS']\n          keyPassword System.env['ANDROID_KEY_PASSWORD'] \n      }\n  }\n}<\/pre>\n\n\n\n<p>We store the <code>key.jks<\/code> as base64 encoded string in Github Secrets and decode it in the workflow:<\/p>\n\n\n\n<pre class=\"syntax\" data-enlighter-language=\"yaml\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">- name: Decode Android Keystore\n  uses: timheuer\/base64-to-file@v1.1\n  with:\n    fileName: 'android\/app\/key.jks'\n    encodedString: ${{ secrets.ANDROID_STORE_CONTENT }}<\/pre>\n\n\n\n\n\n<h2 class=\"wp-block-heading\" id=\"8-release-amp-prod-workflow-\">Release &amp; prod workflow \ud83d\ude80<\/h2>\n\n\n\n<p>The <code>pre-release<\/code> workflow runs for branches that start with <code>release\/v<\/code>. It strips all debugging and internal code to make sure we test the same code that will be released.<\/p>\n\n\n\n<pre class=\"syntax\" data-enlighter-language=\"yaml\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">name: pre-release\non:\n  push:\n    branches:\n      - 'release\/v*'\n# build, test, and post slack message<\/pre>\n\n\n\n<p>Additionally, the <code>pre-release<\/code> workflow posts to various Slack channels to notify QA and marketing teams about a new release using <a href=\"https:\/\/slack.com\/help\/articles\/115005265063-Incoming-webhooks-for-Slack\" rel=\"noreferrer noopener\" target=\"_blank\">incoming webhooks<\/a>.<\/p>\n\n\n\n<p>After everything is tested thoroughly, we create a release on GitHub that triggers the <code>prod<\/code> workflow. It builds and signs the App with production certificates and sends it to the App Store.<\/p>\n\n\n\n<pre class=\"syntax\" data-enlighter-language=\"yaml\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">name: prod\non:\n  release:\n    types:\n      - created\n# build and deplo<\/pre>\n\n\n\n\n\n<h2 class=\"wp-block-heading\" id=\"9-more-tricks-for-your-ci-\">More tricks for your CI \ud83e\uddbe<\/h2>\n\n\n\n<p>If you use the push trigger for GitHub Actions, you will likely run into problems if there are multiple pushes to the same branch in quick succession. More than one instance of the workflow will start and eat build minutes or cause other problems.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>We recommend using the <a href=\"https:\/\/github.com\/marketplace\/actions\/cancel-workflow-action\" target=\"_blank\" rel=\"noreferrer noopener\">Cancel Workflow Action<\/a> to cancel all previous instances of the workflow<\/li>\n\n\n\n<li>If you are looking for an easy and maintainable solution to generate sequential build numbers, try the <a href=\"https:\/\/github.com\/marketplace\/actions\/build-number-generator\" target=\"_blank\" rel=\"noreferrer noopener\">Build Number Generator<\/a>. You can also use the <code>GITHUB_RUN_ID<\/code> but that cannot be customized<\/li>\n\n\n\n<li>Check out the <a href=\"https:\/\/github.com\/marketplace\/clickup\" target=\"_blank\" rel=\"noreferrer noopener\">ClickUp GitHub app<\/a> to see branches, commits, and GitHub status right in your ClickUp tasks. Use <a href=\"https:\/\/docs.clickup.com\/en\/articles\/3904901-automations\">ClickUp Automations<\/a> or <a href=\"https:\/\/clickup.com\/api\">ClickUp&#8217;s public API<\/a> for advanced automations<\/li>\n<\/ul>\n\n\n\n\n\n<h2 class=\"wp-block-heading\" id=\"10-summary-\">Summary \ud83c\udf69<\/h2>\n\n\n\n<p>Building your CI is a fun process. It&#8217;s easy to get started and you can evolve it as you go. Our CI lives after one of ClickUp&#8217;s <a href=\"https:\/\/clickup.com\/about\">Core Values<\/a>: <strong>Progress towards perfection<\/strong>. We&#8217;re constantly working on CI improvements for our QA and engineering teams.<\/p>\n\n\n\n<p>The combination of ClickUp, GitHub Actions, and Fastlane is very powerful and allows building a flexible and fully automated CI\/CD pipeline in less than an hour. Give it a try!<\/p>\n\n\n\n<p>We have many cool topics in the pipeline, so keep checking out the <a href=\"https:\/\/clickup.com\/blog\/engineering-intro\/\">ClickUp Engineering blog<\/a>! \ud83e\udd84<\/p>\n","protected":false},"excerpt":{"rendered":"<p>With a growing mobile team and weekly releases, it&#8217;s essential to have a reliable CI pipeline. We need to be able to build and test our apps and deploy them to the App Store and Google Play Store. We also maintain internal preview versions to test the latest features. Learn how we use ClickUp, Fastlane, [&hellip;]<\/p>\n","protected":false},"author":59,"featured_media":31919,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"ub_ctt_via":"","cu_sticky_sidebar_cta_is_visible":true,"cu_sticky_sidebar_cta_title":"Start using ClickUp today","cu_sticky_sidebar_cta_bullet_1":"Manage all your work in one place","cu_sticky_sidebar_cta_bullet_2":"Collaborate with your team","cu_sticky_sidebar_cta_bullet_3":"Use ClickUp for FREE\u2014forever","cu_sticky_sidebar_cta_button_text":"Get Started","cu_sticky_sidebar_cta_button_link":"","_uf_show_specific_survey":0,"_uf_disable_surveys":false,"footnotes":""},"categories":[909],"tags":[922],"class_list":["post-31914","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-engineering","tag-flutter-ci-and-cd"],"featured_image_src":"https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/01\/flutter-CI-CD.png","author_info":{"display_name":"Simon Leier","author_link":"https:\/\/clickup.com\/blog\/author\/simon\/"},"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v25.6 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Flutter CI &amp; CD at ClickUp | The ClickUp Blog<\/title>\n<meta name=\"description\" content=\"Learn about the tools ClickUp&#039;s Engineering team uses to automate our Continuous Integration (CI) and Continuous Delivery (CD).\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/clickup.com\/blog\/flutter-ci-cd\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Flutter CI &amp; CD at ClickUp | The ClickUp Blog\" \/>\n<meta property=\"og:description\" content=\"Learn about the tools ClickUp&#039;s Engineering team uses to automate our Continuous Integration (CI) and Continuous Delivery (CD).\" \/>\n<meta property=\"og:url\" content=\"https:\/\/clickup.com\/blog\/flutter-ci-cd\/\" \/>\n<meta property=\"og:site_name\" content=\"The ClickUp Blog\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/clickupprojectmanagement\" \/>\n<meta property=\"article:published_time\" content=\"2022-01-19T23:38:53+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-02-18T16:05:56+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/01\/flutter-CI-CD.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1400\" \/>\n\t<meta property=\"og:image:height\" content=\"1050\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Simon Leier\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@clickup\" \/>\n<meta name=\"twitter:site\" content=\"@clickup\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Simon Leier\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"7 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/clickup.com\/blog\/flutter-ci-cd\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/clickup.com\/blog\/flutter-ci-cd\/\"},\"author\":{\"name\":\"Simon Leier\",\"@id\":\"https:\/\/clickup.com\/blog\/#\/schema\/person\/df17fc7404cac8854862adf96ff7282f\"},\"headline\":\"Flutter CI &#038; CD at ClickUp\",\"datePublished\":\"2022-01-19T23:38:53+00:00\",\"dateModified\":\"2026-02-18T16:05:56+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/clickup.com\/blog\/flutter-ci-cd\/\"},\"wordCount\":1052,\"publisher\":{\"@id\":\"https:\/\/clickup.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/clickup.com\/blog\/flutter-ci-cd\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/01\/flutter-CI-CD.png\",\"keywords\":[\"Flutter CI and CD\"],\"articleSection\":[\"Engineering at ClickUp\"],\"inLanguage\":\"en-US\"},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/clickup.com\/blog\/flutter-ci-cd\/\",\"url\":\"https:\/\/clickup.com\/blog\/flutter-ci-cd\/\",\"name\":\"Flutter CI & CD at ClickUp | The ClickUp Blog\",\"isPartOf\":{\"@id\":\"https:\/\/clickup.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/clickup.com\/blog\/flutter-ci-cd\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/clickup.com\/blog\/flutter-ci-cd\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/01\/flutter-CI-CD.png\",\"datePublished\":\"2022-01-19T23:38:53+00:00\",\"dateModified\":\"2026-02-18T16:05:56+00:00\",\"description\":\"Learn about the tools ClickUp's Engineering team uses to automate our Continuous Integration (CI) and Continuous Delivery (CD).\",\"breadcrumb\":{\"@id\":\"https:\/\/clickup.com\/blog\/flutter-ci-cd\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/clickup.com\/blog\/flutter-ci-cd\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/clickup.com\/blog\/flutter-ci-cd\/#primaryimage\",\"url\":\"https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/01\/flutter-CI-CD.png\",\"contentUrl\":\"https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/01\/flutter-CI-CD.png\",\"width\":1400,\"height\":1050},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/clickup.com\/blog\/flutter-ci-cd\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/clickup.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Engineering at ClickUp\",\"item\":\"https:\/\/clickup.com\/blog\/engineering\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Flutter CI &#038; CD at ClickUp\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/clickup.com\/blog\/#website\",\"url\":\"https:\/\/clickup.com\/blog\/\",\"name\":\"The ClickUp Blog\",\"description\":\"The ClickUp Blog\",\"publisher\":{\"@id\":\"https:\/\/clickup.com\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/clickup.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/clickup.com\/blog\/#organization\",\"name\":\"ClickUp\",\"url\":\"https:\/\/clickup.com\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/clickup.com\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/clickup.com\/blog\/wp-content\/uploads\/2025\/07\/logo-v3-clickup-light.jpg\",\"contentUrl\":\"https:\/\/clickup.com\/blog\/wp-content\/uploads\/2025\/07\/logo-v3-clickup-light.jpg\",\"width\":503,\"height\":125,\"caption\":\"ClickUp\"},\"image\":{\"@id\":\"https:\/\/clickup.com\/blog\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/www.facebook.com\/clickupprojectmanagement\",\"https:\/\/x.com\/clickup\",\"https:\/\/www.linkedin.com\/company\/clickup-app\",\"https:\/\/en.wikipedia.org\/wiki\/ClickUp\",\"https:\/\/tiktok.com\/@clickup\",\"https:\/\/instagram.com\/clickup\",\"https:\/\/www.youtube.com\/@ClickUpProductivity\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/clickup.com\/blog\/#\/schema\/person\/df17fc7404cac8854862adf96ff7282f\",\"name\":\"Simon Leier\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/clickup.com\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/f44f9214d107eda091fa30ecceba3fbdd34a0fcd0133f386e112472b8d8d0f99?s=96&d=retro&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/f44f9214d107eda091fa30ecceba3fbdd34a0fcd0133f386e112472b8d8d0f99?s=96&d=retro&r=g\",\"caption\":\"Simon Leier\"},\"description\":\"Simon is a Software Engineer at ClickUp determined to make the mobile app even better. He is a strong advocate of open-source and passionate about Flutter. In the summer he can be found surfing on Lake Garda and in the winter skiing in the Alps.\",\"sameAs\":[\"https:\/\/www.linkedin.com\/in\/simon-leier\/\"],\"url\":\"https:\/\/clickup.com\/blog\/author\/simon\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Flutter CI & CD at ClickUp | The ClickUp Blog","description":"Learn about the tools ClickUp's Engineering team uses to automate our Continuous Integration (CI) and Continuous Delivery (CD).","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/clickup.com\/blog\/flutter-ci-cd\/","og_locale":"en_US","og_type":"article","og_title":"Flutter CI & CD at ClickUp | The ClickUp Blog","og_description":"Learn about the tools ClickUp's Engineering team uses to automate our Continuous Integration (CI) and Continuous Delivery (CD).","og_url":"https:\/\/clickup.com\/blog\/flutter-ci-cd\/","og_site_name":"The ClickUp Blog","article_publisher":"https:\/\/www.facebook.com\/clickupprojectmanagement","article_published_time":"2022-01-19T23:38:53+00:00","article_modified_time":"2026-02-18T16:05:56+00:00","og_image":[{"width":1400,"height":1050,"url":"https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/01\/flutter-CI-CD.png","type":"image\/png"}],"author":"Simon Leier","twitter_card":"summary_large_image","twitter_creator":"@clickup","twitter_site":"@clickup","twitter_misc":{"Written by":"Simon Leier","Est. reading time":"7 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/clickup.com\/blog\/flutter-ci-cd\/#article","isPartOf":{"@id":"https:\/\/clickup.com\/blog\/flutter-ci-cd\/"},"author":{"name":"Simon Leier","@id":"https:\/\/clickup.com\/blog\/#\/schema\/person\/df17fc7404cac8854862adf96ff7282f"},"headline":"Flutter CI &#038; CD at ClickUp","datePublished":"2022-01-19T23:38:53+00:00","dateModified":"2026-02-18T16:05:56+00:00","mainEntityOfPage":{"@id":"https:\/\/clickup.com\/blog\/flutter-ci-cd\/"},"wordCount":1052,"publisher":{"@id":"https:\/\/clickup.com\/blog\/#organization"},"image":{"@id":"https:\/\/clickup.com\/blog\/flutter-ci-cd\/#primaryimage"},"thumbnailUrl":"https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/01\/flutter-CI-CD.png","keywords":["Flutter CI and CD"],"articleSection":["Engineering at ClickUp"],"inLanguage":"en-US"},{"@type":"WebPage","@id":"https:\/\/clickup.com\/blog\/flutter-ci-cd\/","url":"https:\/\/clickup.com\/blog\/flutter-ci-cd\/","name":"Flutter CI & CD at ClickUp | The ClickUp Blog","isPartOf":{"@id":"https:\/\/clickup.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/clickup.com\/blog\/flutter-ci-cd\/#primaryimage"},"image":{"@id":"https:\/\/clickup.com\/blog\/flutter-ci-cd\/#primaryimage"},"thumbnailUrl":"https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/01\/flutter-CI-CD.png","datePublished":"2022-01-19T23:38:53+00:00","dateModified":"2026-02-18T16:05:56+00:00","description":"Learn about the tools ClickUp's Engineering team uses to automate our Continuous Integration (CI) and Continuous Delivery (CD).","breadcrumb":{"@id":"https:\/\/clickup.com\/blog\/flutter-ci-cd\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/clickup.com\/blog\/flutter-ci-cd\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/clickup.com\/blog\/flutter-ci-cd\/#primaryimage","url":"https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/01\/flutter-CI-CD.png","contentUrl":"https:\/\/clickup.com\/blog\/wp-content\/uploads\/2022\/01\/flutter-CI-CD.png","width":1400,"height":1050},{"@type":"BreadcrumbList","@id":"https:\/\/clickup.com\/blog\/flutter-ci-cd\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/clickup.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Engineering at ClickUp","item":"https:\/\/clickup.com\/blog\/engineering\/"},{"@type":"ListItem","position":3,"name":"Flutter CI &#038; CD at ClickUp"}]},{"@type":"WebSite","@id":"https:\/\/clickup.com\/blog\/#website","url":"https:\/\/clickup.com\/blog\/","name":"The ClickUp Blog","description":"The ClickUp Blog","publisher":{"@id":"https:\/\/clickup.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/clickup.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/clickup.com\/blog\/#organization","name":"ClickUp","url":"https:\/\/clickup.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/clickup.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/clickup.com\/blog\/wp-content\/uploads\/2025\/07\/logo-v3-clickup-light.jpg","contentUrl":"https:\/\/clickup.com\/blog\/wp-content\/uploads\/2025\/07\/logo-v3-clickup-light.jpg","width":503,"height":125,"caption":"ClickUp"},"image":{"@id":"https:\/\/clickup.com\/blog\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/clickupprojectmanagement","https:\/\/x.com\/clickup","https:\/\/www.linkedin.com\/company\/clickup-app","https:\/\/en.wikipedia.org\/wiki\/ClickUp","https:\/\/tiktok.com\/@clickup","https:\/\/instagram.com\/clickup","https:\/\/www.youtube.com\/@ClickUpProductivity"]},{"@type":"Person","@id":"https:\/\/clickup.com\/blog\/#\/schema\/person\/df17fc7404cac8854862adf96ff7282f","name":"Simon Leier","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/clickup.com\/blog\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/f44f9214d107eda091fa30ecceba3fbdd34a0fcd0133f386e112472b8d8d0f99?s=96&d=retro&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/f44f9214d107eda091fa30ecceba3fbdd34a0fcd0133f386e112472b8d8d0f99?s=96&d=retro&r=g","caption":"Simon Leier"},"description":"Simon is a Software Engineer at ClickUp determined to make the mobile app even better. He is a strong advocate of open-source and passionate about Flutter. In the summer he can be found surfing on Lake Garda and in the winter skiing in the Alps.","sameAs":["https:\/\/www.linkedin.com\/in\/simon-leier\/"],"url":"https:\/\/clickup.com\/blog\/author\/simon\/"}]}},"reading":["6"],"keywords":[["Engineering at ClickUp","engineering",909]],"redirect_params":{"product":"","department":""},"is_translated":"true","author_data":{"name":"Simon Leier","link":"https:\/\/clickup.com\/blog\/author\/simon\/","image":"https:\/\/clickup.com\/blog\/wp-content\/uploads\/2021\/12\/SL.jpeg","position":"Software Engineer"},"category_data":{"name":"Engineering at ClickUp","slug":"engineering","term_id":909,"url":"https:\/\/clickup.com\/blog\/engineering\/"},"hero_data":{"media_url":"","media_alt_text":"Flutter CI &#038; CD at ClickUp","button":"","template_id":"","youtube_thumbnail_url":"","custom_button_text":"","custom_button_url":"https:\/\/"},"_links":{"self":[{"href":"https:\/\/clickup.com\/blog\/wp-json\/wp\/v2\/posts\/31914","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/clickup.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/clickup.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/clickup.com\/blog\/wp-json\/wp\/v2\/users\/59"}],"replies":[{"embeddable":true,"href":"https:\/\/clickup.com\/blog\/wp-json\/wp\/v2\/comments?post=31914"}],"version-history":[{"count":10,"href":"https:\/\/clickup.com\/blog\/wp-json\/wp\/v2\/posts\/31914\/revisions"}],"predecessor-version":[{"id":594423,"href":"https:\/\/clickup.com\/blog\/wp-json\/wp\/v2\/posts\/31914\/revisions\/594423"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/clickup.com\/blog\/wp-json\/wp\/v2\/media\/31919"}],"wp:attachment":[{"href":"https:\/\/clickup.com\/blog\/wp-json\/wp\/v2\/media?parent=31914"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/clickup.com\/blog\/wp-json\/wp\/v2\/categories?post=31914"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/clickup.com\/blog\/wp-json\/wp\/v2\/tags?post=31914"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}