Streamlining Mobile App Development: A Technical Guide to Mobile CI/CD

Enhance your mobile app development process with advanced CI/CD strategies. This technical guide covers everything you need to build high-quality apps.

Naren · April 11, 2023 · 11 minute read

Introduction

This post is intended for mobile engineers, mobile engineering managers, release engineers, release managers, and CTOs seeking to optimize their mobile app development process. We will discuss the key elements of a mobile CI/CD pipeline and explain their importance.

Whether you're part of an early-stage startup with limited experience in mobile app development, a medium to large enterprise with established teams and processes, or simply interested in learning about best practices and tools for enhancing your mobile app development process, this article will provide valuable insights.

Why is CI/CD crucial for mobile development?

Continuous Integration and Continuous Deployment (CI/CD) offer numerous benefits, such as faster and more frequent releases, improved developer productivity, and reduced risk of human error. Mobile apps, however, face unique challenges that necessitate a robust CI/CD pipeline.

  • Shipping a binary: Unlike web applications or backend servers, mobile apps involve shipping a binary to app stores, which is then installed on users' devices. This makes backward compatibility a critical consideration during development, as controlling the app version users run is impossible. As a result, apps must be tested on various devices and OS versions to ensure seamless functionality.
  • Release management: Mobile app binaries are typically deployed in multiple stages (nightly, alpha, beta, and production) to minimize release risks and reduce the impact of potential issues. A robust release management process involving staged rollouts, feature flags, and monitoring is necessary. This process often engages multiple stakeholders, such as product managers, QA engineers, and release engineers, working together to safely release the app. Automating this process and eliminating manual intervention can significantly decrease time and effort spent on release management, allowing valuable resources to focus on providing value to users.
  • Platform-specific nuances: Mobile development often involves managing multiple platforms (typically iOS & Android), each requiring platform-specific languages, tools, and frameworks. Tools like React Native and Flutter address this issue by offering a single codebase for building apps on both platforms. However, these tools still demand platform-specific build tools and native code for certain features. Consequently, apps must be built and tested on both platforms to ensure proper functionality. For instance, locally setting up all these tools is a time-consuming and error-prone process even for experienced developers.

Therefore, an efficient and dependable CI/CD and release process become vital for delivering high-quality apps and addressing the aforementioned challenges.

Now, let's explore how to achieve this with the help of automation and CI/CD.

Continuous integration

Continuous Integration (CI) automates the process of building, testing, and deploying code with source control. It enables developers to identify and resolve issues early in the development cycle, reducing time and effort spent on debugging. CI also helps enhance code quality and maintainability by enforcing coding standards and best practices before merging code into the main branch.

The goal of CI is to:

  • Catch issues early - Automate tests, static analysis, and code compilation before deploying to production to detect issues early in the development cycle. This helps reduce time and effort spent on debugging and fixing issues.
  • Reduce toil and improve developer experience - Automate tasks such as building, testing, and deploying code to decrease time and effort spent on manual tasks. This improves developer productivity and reduces the risk of human error.
  • Automate tests - Run automated unit, integration, and end-to-end tests to ensure that the code functions as intended.
  • Improve collaboration - Ensure the main branch is always releasable by enforcing code reviews and other best practices.
visual depicting continuous integration for mobile

Continuous Integration consists of a series of steps, called a pipeline, that are executed whenever code is pushed to the repository.

Source control

Source control is an essential part of setting up the CI pipeline. It enables developers to collaborate on code, track changes, and revert to previous versions if necessary. It also helps maintain a history of the codebase, which is useful for debugging and auditing purposes. Popular source control tools include GitHub, GitLab, and Bitbucket.

Compile

This step compiles the code, assembles all the resources (such as strings, icons, and illustrations), and generates a binary. This binary is then used for testing and distribution. Platform-specific nuances must be considered when building apps for different platforms. For example, iOS apps are built using Xcode, which requires a Mac machine, while Android apps are built using Gradle, which can run on any machine.

React Native and Flutter are popular tools for building cross-platform apps. These tools use a single codebase to build apps for both iOS and Android.

visual depicting the build step in continuous integration

Caching and artifact management

Caching and artifact management are crucial components of an efficient CI pipeline, as they significantly impact build times and overall system performance. Caching refers to storing intermediate build artifacts and dependencies for reuse across multiple build runs, while artifact management involves organizing, storing, and retrieving these build artifacts.

By caching intermediate build artifacts, subsequent build runs can reuse them, reducing the time spent on redundant compilation tasks. Additionally, caching dependencies, like third-party libraries and frameworks, can also help speed up the build process, as the pipeline no longer needs to download or build these dependencies from scratch each time.

Artifacts typically include binary files (e.g., APKs or IPAs), symbols files (e.g., dSYMs), and other related assets. By utilizing a dedicated artifact storage solution, such as AWS S3, Google Cloud Storage, or JFrog Artifactory, teams can maintain a centralized repository for their build artifacts, streamlining the retrieval process and simplifying versioning.

Static analysis

Static analysis tools serve as a safety net to catch potential issues that might be overlooked during manual code reviews. They also reduce time spent discussing code style. These tools can be used to identify issues such as:

  • Code style: Detect code style issues like missing semicolons, incorrect indentation, and inconsistent naming conventions.
  • Code quality: Identify code quality issues like unused variables, dead code, and duplicate code.
  • Security: Find security issues like hard-coded secrets, weak encryption, and insecure network requests.
  • Performance: Uncover performance issues like memory leaks, inefficient code, and unnecessary network requests.
  • Accessibility: Reveal accessibility issues like missing labels, low contrast, and absent accessibility traits.
  • Localization: Spot localization issues like missing translations, incorrect pluralization, and lack of RTL support.
  • Documentation: Uncover documentation issues like missing comments and documentation.
  • Dependency analysis: Find dependency issues like unused dependencies, outdated dependencies, and conflicting dependencies.
Visual depicting the various static analysis steps in continuous integration

Adding custom rules to static analysis tools can help detect issues specific to your application. Android Lint, SwiftLint, KTLint, and Detekt enable rule extensions to identify application-specific issues, enforcing coding standards and best practices across your team.

An effective feedback loop for larger teams involves understanding the root cause of issues through postmortems or code reviews, identifying long-term solutions, and then codifying these solutions in custom lint rules to be enforced and automated at the CI layer. This helps avoid repeating mistakes, standardize best practices, and automate processes with continuous integration.

Testing

Automated testing is crucial for the CI process, as it allows for early detection and resolution of issues in the development cycle. Automated testing enables:

  • Verifying functionality: Ensuring the code works as intended guarantees the application functions properly.
  • Detecting regressions: Early detection of regressions in the development cycle reduces time and effort spent debugging and fixing issues.
  • Increased confidence: Automated tests boost confidence in the codebase, making it more reliable for shipping new features.
  • Faster release cycles: Automated tests facilitate faster shipping of new features. As complexity grows, manual testing becomes harder due to the combinatorial explosion, making it impossible to test all possible scenarios.
  • Refactoring: Automated tests make refactoring the codebase easier without introducing regressions.
  • Catching edge cases: Automated tests help identify edge cases that might be missed during manual testing.
  • Tests as documentation: Automated tests act as documentation for the codebase, assisting new developers in onboarding and understanding the codebase.

Automated testing can be divided into multiple pipeline steps, each validating different aspects of the application. Parallelizing these steps can reduce time spent on CI.

Visual depicting various testing steps within continuous integration

Unit tests

Unit tests ensure that individual components of your application function as intended. By validating each component in isolation, it is easier to catch regressions early in the development cycle. Unit tests run quickly, providing faster feedback loops. XCTest for iOS and JUnit and Mockito for Android are widely-used unit testing frameworks.

Test-driven development helps ensure your code works as intended by writing tests before actual code. This approach helps you consider the application requirements and their implementation. It also prevents writing unnecessary code, which can lead to increased complexity and technical debt.

Integration tests

Integration tests confirm that different application components work together correctly. Maestro, Espresso for Android and XCUITest for iOS are popular integration testing tools.

End-to-end tests

End-to-end (E2E) tests are essential for simulating real-world user interactions with your app. They cover the entire application flow, enabling developers to detect issues from the user's perspective and enhance the overall user experience. Integrating E2E tests into your CI pipeline helps ensure a seamless and enjoyable user experience. Maestro, Appium and Detox are popular E2E testing frameworks for mobile apps.

Visual regression testing

Visual regression testing is an effective method for ensuring your app appears as intended across various devices and screen sizes. This approach involves comparing screenshots of your app's UI with a baseline, allowing you to detect visual changes that might be missed during manual testing. By identifying UI discrepancies, visual regression testing ensures your app maintains a polished and professional appearance. Paparazzi is an excellent visual regression testing tool for Android and Swift snapshot testing for iOS.

Memory leak detection

Memory leaks occur when an object, no longer needed, is still referenced by the app, causing it to remain in memory. This can result in performance issues and crashes. Memory leak detection tools aid in identifying and resolving memory leaks before they cause problems. Proactively addressing these issues by incorporating memory leak detection tools into your CI pipeline is a smart strategy. LeakCanary for Android and Xcode Memory Graph Debugger for iOS are popular memory leak detection tools.

Continuous Deployment

Continuous deployment (CD) automatically deploys code changes to various environments such as nightly, alpha, beta and production. It's an extension of continuous integration, where the code is automatically deployed to a staging environment after it passes all the tests in the CI pipeline. CI is crucial to enable CD, as it ensures the code is in a good state before deployment.

The benefits of CD include:

  • Faster releases: Developers can deploy code changes faster and enable faster feedback loops.
  • Reduce toil and improve developer experience - By automating the deployment process, you can reduce the risk of human error, minimize toil and make it easier to deploy code changes. Developers can focus on writing code instead of spending time on manual deployments.
  • Hotfixes: Reduce the effort to deploy hotfixes to production enabling faster time to recovery.
Visual depicting continuous deployment for mobile

Let's now explore the different components involved in CD:

Versioning

Versioning involves assigning a unique version name or number to an app to identify the code shipped in the binary. The App Store and Play Store require a version name or number to distribute the apps. It's essential to use a consistent versioning scheme across all your apps to avoid confusion. While both iOS and Android apps require versioning, the schemes for each platform have some differences. Understanding these distinctions is crucial for maintaining consistency and clarity across your app's releases.

iOS

For iOS apps, the bundle short version string (e.g., 1.0.0) and bundle version (e.g., 1) are used to identify the app binary. The bundle short version string is displayed to users in the App Store to identify the app binary. The bundle version is used to identify the app binary and is not displayed to users.

Android

For Android apps, the version name is a string that usually follows the semantic versioning format (e.g., 1.0.0) but could be any string. The version code is a monotonically increasing integer incremented for each new app version (e.g., 1). The version name and code are displayed to users in the Play Store to identify the app binary.

To learn more about semantic versioning, check out our docs on version bumping.

Here's a quick visual of what the version bump would look like for both iOS and Android apps:

Visual depicting a comparison between version bumping of Android & iOS

Date versioning (e.g., 2020.01.01) is another standard versioning scheme for iOS and Android apps.

Signing

The signing step is essential for verifying the authenticity and integrity of your apps, allowing them to be distributed through app stores.

iOS

For iOS app signing, you need to work with the following components:

Certificates: Two types of certificates are involved in iOS app signing: Development and Distribution certificates.

  • Development certificates are used for signing apps during development, allowing them to run on physical devices.
  • Distribution certificates are used for signing apps when submitting them to the App Store or distributing them via ad hoc or enterprise distribution.

Provisioning Profiles: These profiles link a specific app with a set of devices (for development) or enable distribution via the App Store or other methods (for distribution). There are four types of provisioning profiles:

  • Development: Used for running the app on physical devices during development.
  • Ad-Hoc Distribution: Enables distribution to a limited number of devices (100) for testing outside the App Store.
  • In-House / Enterprise distribution: Allows distribution to over 100 devices within an organization without going through the App Store.
  • App Store Distribution: Required for submitting the app to the App Store.

The provisioning profile includes information about the app, the certificate used to sign it, and the devices that can install it. Note that if you change the certificate, you must create a new provisioning profile.

In the CD pipeline, import the appropriate certificate and provisioning profile into the build environment. The app's binary will be signed using the certificate, and the provisioning profile will be embedded.

Android

For Android app signing, you need to work with the following components:

  • Keystore: A binary file containing a set of private keys for signing Android apps. When signing an app, a private key from the keystore generates a digital signature attached to the app's APK.
  • Keystore password: Protects the entire keystore file, required to access the keystore and its keys.
  • Key alias: A unique name given to a key within your keystore. You can have multiple keys within a keystore, with each key used to sign different apps or versions of the same app.
  • Key password: Protects the private key within the Keystore, required to access the private key when signing the app.

In the CD pipeline, import the keystore file, key alias, and associated passwords into the build environment. The app's APK/AAB will be signed using the private key from the Keystore.

Visual depicting the signing process in continuous deployment

Here's a sample GitHub workflow for both iOS and Android app signing.

Note: On iOS, the binary must be signed with a development or ad-hoc provisioning profile and certificate to run on a device. In contrast, on Android, the debug app (apk) can be installed directly on a device without any additional steps.

Distribution

Distribution involves making the app available to users. There are several ways to distribute apps:

  • App stores: The most common method, providing a centralized location for users to discover and download apps. The two primary app stores are the App Store for iOS apps and Google Play for Android apps.
  • Over-the-air (OTA) distribution: This method distributes apps outside of app stores, sending the app binary to users directly via email or a custom distribution platform like App Centeror Firebase App Distribution. It is suitable for distributing apps to limited users for testing purposes.
  • Enterprise distribution: This method is for distributing apps to users within an organization. It involves sending the app binary to users directly via email or a custom distribution platform. Enterprise distribution helps distribute apps to many users within an organization.

Tracks/Channels

App stores and other distribution platforms offer various tracks or channels for app distribution, each serving a specific purpose:

  • Internal/Nightly track: This track, used for daily distribution, delivers the latest app version from the main branch directly to users within the organization for testing.
  • Alpha track: Typically distributed weekly or biweekly, the alpha track is for testing changes within the organization before they become available in the beta track.
  • Beta track: The beta track distributes the app to a limited number of users outside the organization for testing, usually on a weekly or biweekly basis, to collect feedback from real production users.
  • Production track: Making the app available to all users, the production track is distributed regularly, either weekly or biweekly.
Visual depicting the various tracks for deploying an app

Automation is key to streamlining the distribution process, allowing the team to focus on app development. This enables the team to focus on building the app and not worry about the distribution process. The most common way to automate the distribution process is to use tools like Fastlane, Wolfia, Bitrise, and Runway.

These tools provide a centralized location for managing the distribution process and various levels of automation. For example, fastlane provides a command line interface for managing the distribution process, and Wolfia provides a web interface for managing the distribution process and automates the monitoring and rolling out of the app.

App Rollout

Rolling out an app involves making it available to users in the production track. It's essential to roll out the app to a small percentage of users first and then gradually increase the percentage of users to ensure that the app is stable and ready for production. This is called a phased rollout/staged rollout and reduces the risk of releasing a buggy app to all users. Monitoring the app to ensure it's stable and ready for production is also essential.

Here's a quick visual of what a rollout would look like:

Visual depicting the app rollout process

This process of monitoring and rolling out the app to the appropriate track/channel can be automated using tools like Wolfia. Release management is a vital part of releasing a stable app to production. Wolfia automates rolling out the app to the appropriate track/channel and automating the monitoring so that the rollout can be halted if the app crosses the threshold of crashes. This reduces the blast radius and the impact of a bug on your users.

Monitoring

After distributing the app to the appropriate track/channel, monitoring its stability and production readiness is crucial. Proactive identification and resolution of issues help maintain a high-quality user experience and optimal app performance.

We wrote a detailed blog post on monitoring mobile apps if you want to learn more about it.

In a future post, we will explore automating your release train and best practices for on-call rotations to address production issues while minimizing user experience impact.

Final thoughts

This post has examined the various steps involved in releasing a mobile app to production, as well as tools and methods for streamlining the process.

Our goal at Wolfia is to help mobile teams release high-quality apps to production quickly and frequently, with reduced risk and effort. We automate the entire release process, providing a centralized location for managing it.

To learn more about Wolfia, visit our website, explore our documentation, or contact us.

You build it. We ship it.

Try for free ->