Continuous Integration for Flutter Apps on iOS with Semaphore

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 conceptsand my previous article, Flutter apps in Android using Semaphore to give you head start.

Building iOS apps with Flutter

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.

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.

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 appmay consist of one or more workflow pipelines. Each workflow pipeline can have one or more blocksexecuted 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 sectionto 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.

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.

Originally published at https://semaphoreci.com on December 2, 2021.

--

--

--

Supporting developers with insights and tutorials on delivering good software. · https://semaphoreci.com

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Build a Search View in SwiftUI

SwiftUI: Rendering Mode

Swift 5 and ABI stability — the best time to migrate

How to implement Drag and Drop in iOS

You can now try 1Password’s customizable redesign on your iPhone and iPad : Gadget Game News

How to Set Up Code Generation Tools in Your Swift Project

If you want to use super-resolution for the time being, use the CoreML model that can be used with…

My Experience of Escaping the Tutorial Island

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Semaphore

Semaphore

Supporting developers with insights and tutorials on delivering good software. · https://semaphoreci.com

More from Medium

Deploy Flutter (iOS) Apps to Testers using Firebase App Distribution with Semaphore

Bitrise Configuration for flutter IOS CI/Cd pipeline for Firebase and Appstore

Retriable API Calls with Modern Swift

Retriable API Calls with Modern Swift

Mobile Platform MQTT implementation using Adapter Design Pattern