2 Dec 2021 · Software Engineering

    Continuous Integration for Flutter Apps on iOS with Semaphore

    9 min read
    Contents

    Publishing iOS apps is known to have a complex and tedious process in the developer community. The publishing process includes dealing with code signing the builds and the review process set by Apple to decide whether it meets their standards for public release. And to help you get there faster, you’ll need a good set of tooling for automating the tests and deployment of your app.

    That’s where the Continuous Integration (CI) philosophy helps. It mainly encourages developers to automate the processes of testing and merging commits in a single project to ensure code quality and stability of the product.

    In this article, you will learn how to set up a CI pipeline for your Flutter apps in iOS using Semaphore.

    Prerequisites

    • Flutter SDK 2.0 and up
    • An existing Flutter project or use this starter project

    I recommend reading the Semaphore core concepts and my previous article, Flutter apps in Android using Semaphore to give you head start.

    Building iOS apps with Flutter

    The official website of Flutter. See flutter.dev

    Flutter is a UI toolkit for crafting beautiful user interfaces and experiences for your ideas and apps. Sharing code between Android and iOS requires little to no platform specific configuration code change. It also supports a rich set of packages that enables you to follow the industry standard design languages like Material and Cupertino theme.

    Project Overview

    The project is a simple todo app. It is written in Flutter 2.0 with support for null safety and includes unit and widgets tests, and UI tests.

    iOS app development

    Developing iOS apps requires Xcode and a macOS machine. Unlike developing on Android, it can run on Windows, Linux and macOS. You’ll also need devices like iOS or use the prebuilt simulators on your machine.

    The Flutter iOS project running on the latest version of Xcode (13.1)

    Creating deployments for Flutter in iOS requires you to have an Apple developer account and register a unique bundle identifier for your project. More on this.

    To make sure your Flutter apps are ready for iOS development, run flutter doctor:

    flutter doctor -v 
    … 
    
    [✓] Xcode - develop for iOS and macOS
        • Xcode at /Applications/Xcode.app/Contents/Developer
        • Xcode 12.5.1, Build version 12E507
        • CocoaPods version 1.11.2
    
    [✓] Connected device (3 available)
        • iPhone SE (2nd generation) (mobile) • 696A6A49-8419-4E05-9D95-9EEC78B8084F • ios            • com.apple.CoreSimulator.SimRuntime.iOS-14-5 (simulator)
        • macOS (desktop)                     • macos                                • darwin-x64     • macOS 11.5 20G71 darwin-x64
        • Chrome (web)                        • chrome                               • web-javascript • Google Chrome 96.0.4664.55

    Running the flutter doctor gives you a diagnosis of the Flutter SDK and Xcode installation, and devices available for testing. If all items are checked, you’re good to go.

    Continuous Integration for iOS

    The Xcode requirement for developing iOS apps means that the CI environment that you’ll be using for the project is going to need a virtual machine (VM) with macOS as its operating system.

    Continuous Integration with Semaphore

    Before proceeding, I recommend reading Semaphore’s core concepts for you to understand the foundations of what makes it a fast and robust CI/CD service for your apps. Semaphore is user-friendly enough that most steps discussed here are visual, and no editing of YAML files is required.

    Understanding the Visual Builder

    Semaphore’s Visual Builder

    Semaphore’s Visual Builder is an intuitive way to build your CI pipeline. An entire workflow for your app may consist of one or more workflow pipelines. Each workflow pipeline can have one or more blocks executed sequentially, by default, from left to right. And, each block can have jobs (tasks or commands) that can run in parallel.

    This article is focused on Flutter on iOS so that you will have to use the macOS VM with the macos-xcode13 image.

    Setting up your project in Semaphore

    By now, you should have a good idea what Semaphore is. Let’s start building our continuous integration pipeline by creating or logging in with your account with Semaphore

    From the top left corner, tap + Create New to create a new project.

    Next, select the desired project from your Github account.

    Then, click Customize to set up our custom workflow from scratch.

    That’s it, you’ve added your project to Semaphore. Let’s create your first workflow pipeline!

    Setting up your first block

    Install dependencies is a block that downloads and caches the Flutter SDK and other dependencies on the VM.

    Create a block called Install Dependencies.

    Next, add a job named Install and cache Flutter and the following commands:

    checkout
    cache restore flutter-packages-$SEMAPHORE_GIT_BRANCH-$(checksum pubspec.yaml),flutter-packages-$(checksum pubspec.yaml),flutter-packages
    flutter pub get
    cache store flutter-packages-$SEMAPHORE_GIT_BRANCH-$(checksum pubspec.yaml),flutter-packages-$(checksum pubspec.yaml),flutter-packages /root/.pub-cache

    (Optional) If you want to test if it’s working, click Run the workflow, then click Edit Workflow to continue updating the workflow.

    Setting up lint

    Lint is a block that’s going to check the code quality of your project. This lint block has two jobs that run in parallel. If either one of the jobs fails, the lint block stops running. Running jobs in parallel is helpful if you want to maximize speed, especially when jobs don’t have to depend on each other

    Create a block called Lint.

    Next, add a job named Format and add the command.

    flutter format --set-exit-if-changed .

    Then, add a job named Analyze and add the command

    flutter analyze .

    This project uses lint rules set by the very_good_analysis package, which should be good enough to get you started writing and enforce high quality code in your team.

    Lastly, to use the cached dependencies, add the following to the Prologue section of the block.

    checkout
    cache restore flutter-packages-$SEMAPHORE_GIT_BRANCH-$(checksum pubspec.yaml),flutter-packages-$(checksum pubspec.yaml),flutter-packages
    flutter pub get

    Prologue executes the command(s) you’ve added before executing each job.

    (Optional) If you want to test if the lint block is working, click Run the workflow, then click Edit Workflow to continue updating the workflow.

    Running tests

    Run tests is a block that contains both unit and widget tests for your Flutter apps. These tests are located in the test directory in the root folder. 

    Create a block named Run tests. Use Lint as the dependency of this block so it won’t run if Lint block fails.

    Next, add a job named Unit and widget tests and add the following:

    flutter test test

    Lastly, to use of the cached dependencies, add the following to the Prologue section of the block.

    checkout
    cache restore flutter-packages-$SEMAPHORE_GIT_BRANCH-$(checksum pubspec.yaml),flutter-packages-$(checksum pubspec.yaml),flutter-packages
    flutter pub get

    (Optional) If you want to test if the test block is working, click Run the workflow, then click Edit Workflow to continue updating the workflow.

    Running UI tests

    UI or integration tests allow you to test one or more user flows integrated in your app. For Flutter, we are using the official `integration_test` package which contains the tools for you to write and run simulated tests on test devices like Simulators for iOS.

    Create a block called Run UI tests.

    Next, add a job named Add new item and add the following:

    flutter test integration_test/add_new_todo_item_test.dart

    Repeat the same step for the rest of the integration tests found under the integration_test directory.

    Next, add the following code in the Prologue section for reusing cached dependencies:

    checkout
    cache restore flutter-packages-$SEMAPHORE_GIT_BRANCH-$(checksum pubspec.yaml),flutter-packages-$(checksum pubspec.yaml),flutter-packages
    flutter pub get

    Lastly, add the following at the bottom of the Prologue section to set up simulator before running the integration tests:

    device_uuid=$(xcrun simctl create ios-simulator com.apple.CoreSimulator.SimDeviceType.iPhone-11-Pro com.apple.CoreSimulator.SimRuntime.iOS-14-5)
    xcrun simctl boot $device_uuid

    (Optional) If you want to select a different simulator, run xcrun simctl list and it should display the list of available simulators from the VM.

    Creating build archive

    Finally, you need to check if the project is buildable with the incoming changes from a pull request or commit.

    Create a block named Build Artifact.

    Next, add a job named Generate IPA or Artifact with the following:

    flutter build ios --no-codesign
    artifact push job build/ios/iphoneos/Runner.app

    The artifact is a command used to upload files like IPA or Runner.app files in Flutter to Semaphore for later use.

    Lastly, to use of the cached dependencies, add the following to the Prologue section of the block.

    checkout
    cache restore flutter-packages-$SEMAPHORE_GIT_BRANCH-$(checksum pubspec.yaml),flutter-packages-$(checksum pubspec.yaml),flutter-packages
    flutter pub get

    (Bonus) Sending Slack notifications 

    Semaphore supports sending Slack notifications without you having to deal with the complexities involved in it. Setting up Slack notifications is helpful if you want to notify your team whenever a new build archive gets uploaded and code changes are failing during integration.

    Slack message triggered by Semaphore after a successful build.

    Set up a new Slack Notification in Semaphore Settings

    On the top right corner, tap your Account, then click on Settings.


    Create a new Slack notification

    Make sure to add the project name so the workflow will only run for that specific project so that you will not incur unnecessary build runs.

    Set up incoming webhook URL

    You need to create an incoming webhook URL in your Slack workspace and enter the values for the Slack endpoint and channel(s) here.

    Here’s the final workflow of the project:

    You can download the final project here.

    Conclusion

    Ensuring that the product you’re releasing to your users is usable and stable at scale requires more than just manual testing and deployment. Investing in tools like CI pipelines and automation offloads most of the repetitive and ad hoc work for you. Semaphore helps you achieve this without you writing many lines of code while setting up your CI pipelines. Having a good set of tests and stable CI pipelines gives you more time to focus on what truly matters, which is building solutions for your users.

    Leave a Reply

    Your email address will not be published. Required fields are marked *

    Avatar
    Writen by:
    I'm a software engineer focusing on delivering products that help improve people's lives. I help build a platform that enables everyone to invest in people and ideas. I collaborate with the tech community regarding developer experiences and tools like Flutter and Dart. I previously helped launch products used by millions globally.