Publishing iOS apps is 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 Fluter 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 a 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 minimal 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 (missing image)
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 (missing image)
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 you’ll be using for the project will need a virtual machine (VM) with macOS as its operating system.
Continuous Integration with Semaphore
Before proceeding, I recommend reading Semaphore’s core concepts to understand 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) running in parallel.
This article focuses on Flutter on iOS, so you must use the macOS VM with the macOS-xcode13 image.
Setting up your project in Semaphore
By now, you should have a good idea of 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 are 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 it.
Setting up lint
Lint is a block that will check your project’s code quality. 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 enforcing 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 the Lint block fails.
Next, add a job named Unit and widget tests and add the following:
flutter test test
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
(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 use the official `integration_test` package which contains the tools 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
, which 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 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 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
Sending Slack notifications
Semaphore supports sending Slack notifications without you having to deal with its complexities. Setting up Slack notifications is helpful if you want to notify your team whenever a new build archive gets uploaded and code changes fail during integration.
Semaphore triggers the Slack message after a successful build.
Set up a new Slack Notification in Semaphore Settings
On the top right corner, tap your Account, then click Settings.
Create a new Slack notification.
Make sure to add the project name so the workflow will only run for that specific project, and you will not incur unnecessary build runs
Set up incoming webhook URL
You must 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 the product you release 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 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: building solutions for your users.
Originally published at https://semaphoreci.com/blog/flutter-ios-apps on December 2, 2021.