How to Implement CI/CD Pipelines for Flutter, React Native and .NET MAUI

How to Implement CI/CD Pipelines for Flutter, React Native and .NET MAUI

Jan Thalheim
Jan Thalheim
7 min read

Continuous Integration and Continuous Deployment (CI/CD) pipelines have become essential for modern mobile app development. They automate testing, building, and deploying apps, ensuring consistent quality while reducing manual effort. This comprehensive guide will walk you through implementing effective CI/CD pipelines for three popular cross-platform frameworks: Flutter, React Native, and .NET MAUI.

Introduction to CI/CD for Mobile Development

CI/CD practices offer several significant benefits for mobile app developers:

  • Faster release cycles: Automate time-consuming build and deployment processes
  • Higher quality: Catch bugs early through automated testing
  • Consistency: Ensure builds are reproducible across environments
  • Team efficiency: Free developers from repetitive tasks

While the specific implementation details vary between frameworks, the core principles remain consistent: automate everything, test thoroughly, and deploy reliably.

Key CI/CD Platforms for Mobile Development

Before diving into framework-specific configurations, let's briefly examine the platforms we'll be working with:

GitHub Actions

  • Tightly integrated with GitHub repositories
  • Flexible workflow configuration via YAML
  • Free tier for public repositories
  • Growing mobile-specific action ecosystem

Bitrise

  • Mobile-first CI/CD platform
  • Extensive library of pre-built steps for mobile workflows
  • Excellent support for code signing and provisioning
  • Intuitive workflow editor

Azure DevOps

  • Comprehensive DevOps toolchain
  • Powerful integration with Microsoft services
  • Flexible build agents and deployment targets
  • Advanced pipeline configuration options

Let's now explore how to implement CI/CD for each framework.

Flutter CI/CD Implementation

Flutter's growing popularity for cross-platform development makes it an excellent candidate for CI/CD automation.

Setting Up GitHub Actions for Flutter

Create a .github/workflows/flutter-ci.yml file in your repository:

name: Flutter CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Setup Flutter
        uses: subosito/flutter-action@v2
        with:
          flutter-version: "3.19.0"
          channel: "stable"

      - name: Install dependencies
        run: flutter pub get

      - name: Analyze project source
        run: flutter analyze

      - name: Run tests
        run: flutter test

      - name: Build APK
        run: flutter build apk --release

      - name: Upload APK
        uses: actions/upload-artifact@v3
        with:
          name: release-apk
          path: build/app/outputs/flutter-apk/app-release.apk

Bitrise Configuration for Flutter

  1. Add your Flutter project to Bitrise
  2. Use the Flutter App workflow template
  3. Configure your workflow with these key steps:
# bitrise.yml snippet
workflows:
  primary:
    steps:
      - activate-ssh-key@4:
          run_if: '{{getenv "SSH_RSA_PRIVATE_KEY" | ne ""}}'
      - git-clone@6: {}
      - flutter-installer@0:
          inputs:
            - version: stable
      - cache-pull@2: {}
      - flutter-analyze@0:
          inputs:
            - project_location: "$BITRISE_FLUTTER_PROJECT_LOCATION"
      - flutter-test@0:
          inputs:
            - project_location: "$BITRISE_FLUTTER_PROJECT_LOCATION"
      - flutter-build@0:
          inputs:
            - project_location: "$BITRISE_FLUTTER_PROJECT_LOCATION"
            - platform: both
      - deploy-to-bitrise-io@2: {}
      - cache-push@2: {}

Azure DevOps Pipeline for Flutter

Create an azure-pipelines.yml file with:

trigger:
  - main

pool:
  vmImage: "ubuntu-latest"

steps:
  - task: FlutterInstall@0
    inputs:
      channel: "stable"
      version: "latest"

  - task: FlutterCommand@0
    inputs:
      projectDirectory: "."
      command: "pub"
      arguments: "get"

  - task: FlutterCommand@0
    inputs:
      projectDirectory: "."
      command: "analyze"

  - task: FlutterTest@0
    inputs:
      projectDirectory: "."

  - task: FlutterBuild@0
    inputs:
      projectDirectory: "."
      target: "aab"
      buildNumber: "$(Build.BuildNumber)"

  - task: PublishBuildArtifacts@1
    inputs:
      PathtoPublish: "$(Build.SourcesDirectory)/build/app/outputs/bundle/release/app-release.aab"
      ArtifactName: "android-bundle"
      publishLocation: "Container"

React Native CI/CD Implementation

React Native remains extremely popular for JavaScript developers looking to build cross-platform mobile apps.

GitHub Actions for React Native

Create a .github/workflows/react-native-ci.yml file:

name: React Native CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  build-android:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: "18"

      - name: Install dependencies
        run: npm ci

      - name: Run ESLint
        run: npx eslint .

      - name: Run Jest tests
        run: npm test

      - name: Setup Java JDK
        uses: actions/setup-java@v3
        with:
          distribution: "temurin"
          java-version: "17"

      - name: Build Android Release
        run: |
          cd android
          ./gradlew bundleRelease

      - name: Upload Android Bundle
        uses: actions/upload-artifact@v3
        with:
          name: app-release-bundle
          path: android/app/build/outputs/bundle/release/app-release.aab

Bitrise Configuration for React Native

Configure your bitrise.yml with:

# bitrise.yml snippet for React Native
workflows:
  primary:
    steps:
      - activate-ssh-key@4: {}
      - git-clone@6: {}
      - npm@1:
          inputs:
            - command: ci
      - npm@1:
          inputs:
            - command: test
      - android-build@1:
          inputs:
            - project_location: "$BITRISE_SOURCE_DIR/android"
            - module: "app"
            - variant: "release"
      - deploy-to-bitrise-io@2: {}
      - cache-push@2: {}

Azure DevOps Pipeline for React Native

Create an azure-pipelines.yml file:

trigger:
  - main

pool:
  vmImage: "macos-latest"

steps:
  - task: NodeTool@0
    inputs:
      versionSpec: "18.x"
    displayName: "Install Node.js"

  - script: |
      npm ci
    displayName: "Install dependencies"

  - script: |
      npm test
    displayName: "Run tests"

  - task: JavaToolInstaller@0
    inputs:
      versionSpec: "17"
      jdkArchitectureOption: "x64"
      jdkSourceOption: "PreInstalled"

  - script: |
      cd android
      ./gradlew bundleRelease
    displayName: "Build Android Release"

  - task: PublishBuildArtifacts@1
    inputs:
      PathtoPublish: "$(Build.SourcesDirectory)/android/app/build/outputs/bundle/release"
      ArtifactName: "android-release"
      publishLocation: "Container"

.NET MAUI CI/CD Implementation

Microsoft's .NET Multi-platform App UI (.NET MAUI) is the evolution of Xamarin.Forms, enabling you to build cross-platform apps with C# and XAML.

GitHub Actions for .NET MAUI

Create a .github/workflows/maui-ci.yml file:

name: .NET MAUI CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  build-android:
    runs-on: windows-latest
    steps:
      - uses: actions/checkout@v3

      - name: Setup .NET
        uses: actions/setup-dotnet@v3
        with:
          dotnet-version: "8.0.x"

      - name: Install MAUI Workload
        run: dotnet workload install maui

      - name: Restore dependencies
        run: dotnet restore

      - name: Build Android App
        run: dotnet build -f net8.0-android -c Release

      - name: Upload Android Artifacts
        uses: actions/upload-artifact@v3
        with:
          name: android-artifacts
          path: bin/Release/net8.0-android/**/*.apk

Bitrise Configuration for .NET MAUI

Configure your bitrise.yml with:

# bitrise.yml snippet for .NET MAUI
workflows:
  primary:
    steps:
      - activate-ssh-key@4: {}
      - git-clone@6: {}
      - nuget-restore@1: {}
      - script@1:
          inputs:
            - content: |-
                #!/bin/bash
                dotnet workload install maui
                dotnet build -f net8.0-android -c Release
      - deploy-to-bitrise-io@2: {}

Azure DevOps Pipeline for .NET MAUI

Create an azure-pipelines.yml file:

trigger:
  - main

pool:
  vmImage: "windows-latest"

variables:
  buildConfiguration: "Release"

steps:
  - task: UseDotNet@2
    inputs:
      packageType: "sdk"
      version: "8.0.x"
      includePreviewVersions: true

  - script: dotnet workload install maui
    displayName: "Install MAUI workload"

  - task: DotNetCoreCLI@2
    inputs:
      command: "restore"
      projects: "**/*.csproj"
      feedsToUse: "select"

  - task: DotNetCoreCLI@2
    inputs:
      command: "build"
      projects: "**/*.csproj"
      arguments: "-f net8.0-android -c $(buildConfiguration)"

  - task: PublishBuildArtifacts@1
    inputs:
      PathtoPublish: "bin/$(buildConfiguration)/net8.0-android"
      ArtifactName: "android-app"
      publishLocation: "Container"

Best Practices for Mobile CI/CD

Regardless of the framework you're using, follow these best practices:

  1. Code signing management: Use secure methods to manage certificates and profiles

    • GitHub Actions: Use secrets for storing credentials
    • Bitrise: Use the Code Signing tab
    • Azure DevOps: Use secure files and variable groups
  2. Environment-specific configurations: Separate dev, staging, and production builds

    • Use environment variables or build variants
    • Implement feature flags for controlled rollouts
  3. Automated testing at multiple levels:

    • Unit tests for business logic
    • Integration tests for component interactions
    • UI tests for critical user flows
  4. Versioning strategy:

    • Implement semantic versioning
    • Automate version bumping based on commit messages or branch names
  5. Distribution channels:

    • Configure automated deployments to app stores
    • Set up beta testing programs (TestFlight, Google Play Beta)
    • Consider internal distribution for QA teams

Common Challenges and Solutions

Managing iOS Certificates and Provisioning Profiles

Challenge: iOS code signing is notoriously complex.

Solution:

  • Use fastlane match with a private Git repository
  • Implement Bitrise's iOS Code Signing feature
  • Store certificates and profiles securely in Azure KeyVault

Handling Long Build Times

Challenge: Mobile CI/CD pipelines can be time-consuming.

Solution:

  • Implement caching for dependencies
  • Use incremental builds when possible
  • Consider parallelizing platform-specific builds

Ensuring Consistent Environments

Challenge: Different CI environments can lead to inconsistent builds.

Solution:

  • Use containerization (Docker) where possible
  • Pin dependency versions strictly
  • Document environment requirements thoroughly

Conclusion

Implementing CI/CD pipelines for Flutter, React Native, and .NET MAUI requires initial investment but pays significant dividends through improved quality, faster releases, and reduced manual effort. Each framework has its unique considerations, but the fundamental CI/CD principles remain consistent.

By following the implementation guides and best practices outlined in this article, you can create robust automation for your mobile app development workflow, regardless of which cross-platform technology you choose. Start small, focus on the most impactful automation first, and gradually expand your pipeline as your team gains confidence in the process.

Remember that CI/CD is not just about tooling but also about fostering a culture of quality and automation within your development team. Encourage developers to write tests, fix broken builds promptly, and continuously improve the pipeline itself.

Happy building!

Ready to streamline your internal app distribution?

Start sharing your app builds with your team and clients today.
No app store reviews, no waiting times.