Skip to content

Mobile Build & Release

This page covers how to build APKs and IPAs, how environment flavors work, signing configuration, and the release process for all three mobile apps.


Build Commands (Melos)

All three apps use the same Melos command surface. Run commands from inside the app directory.

bash
# First-time setup (clones submodules, runs pub get, generates code)
melos run init

# Install / update dependencies
melos run get

# Run in debug mode
melos run run

# Build Android APK (debug)
melos run build-apk

# Run tests
melos run test

Environment Flavors

Each app supports three environments via Flutter --dart-define variables:

EnvironmentAPI_BASE_URLKEYCLOAK_URLPurpose
devhttps://gateway.microtec-test.comhttps://keycloak.microtec-test.comDevelopment and feature testing
stagehttps://gateway.microtecstage.comhttps://keycloak.microtecstage.comStaging / UAT
productionhttps://gateway.onlinemicrotec.com.sahttps://keycloak.onlinemicrotec.com.saLive production

No Flutter flavors configured

The apps do not use Flutter's native flavors configuration in android/app/build.gradle or Xcode schemes. Environment selection happens entirely through --dart-define at build time. This simplifies the build configuration at the cost of requiring the caller to specify the correct defines.


Building Android APK

Debug APK

bash
flutter build apk \
  --dart-define=API_BASE_URL=https://gateway.microtecstage.com \
  --dart-define=KEYCLOAK_URL=https://keycloak.microtecstage.com

Output: build/app/outputs/flutter-apk/app-release.apk

Release APK (signed)

bash
flutter build apk --release \
  --dart-define=API_BASE_URL=https://gateway.onlinemicrotec.com.sa \
  --dart-define=KEYCLOAK_URL=https://keycloak.onlinemicrotec.com.sa

For a signed release APK, the keystore must be configured first (see Android Signing below).

bash
flutter build appbundle --release \
  --dart-define=API_BASE_URL=https://gateway.onlinemicrotec.com.sa \
  --dart-define=KEYCLOAK_URL=https://keycloak.onlinemicrotec.com.sa

Output: build/app/outputs/bundle/release/app-release.aab


Building iOS IPA

Prerequisites

  • macOS with Xcode 15+
  • Valid Apple Developer account with provisioning profile
  • App registered in App Store Connect

Build

bash
flutter build ipa --release \
  --dart-define=API_BASE_URL=https://gateway.onlinemicrotec.com.sa \
  --dart-define=KEYCLOAK_URL=https://keycloak.onlinemicrotec.com.sa \
  --export-options-plist=ios/ExportOptions.plist

Output: build/ios/ipa/<AppName>.ipa

The ExportOptions.plist file in ios/ specifies the export method (app-store, ad-hoc, or enterprise) and the provisioning profile.


Android Signing

Keystore setup

Each app has a keystore file that must NOT be committed to the repository. The keystore is stored in Azure Key Vault and retrieved by the CI/CD pipeline.

For local builds, create a key.properties file in the app's android/ directory:

properties
# android/key.properties  — DO NOT COMMIT THIS FILE
storePassword=<keystore-password>
keyPassword=<key-password>
keyAlias=<key-alias>
storeFile=<path-to-keystore.jks>

The android/app/build.gradle reads this file:

groovy
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
    keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}

android {
    signingConfigs {
        release {
            keyAlias keystoreProperties['keyAlias']
            keyPassword keystoreProperties['keyPassword']
            storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
            storePassword keystoreProperties['storePassword']
        }
    }
    buildTypes {
        release {
            signingConfig signingConfigs.release
        }
    }
}

Never commit key.properties or .jks files

The android/key.properties and *.jks keystore files are in .gitignore. Committing them would expose signing credentials. The CI/CD pipeline injects these from Azure Key Vault at build time.


iOS Signing

iOS signing is managed through Xcode's automatic provisioning in CI and manual configuration locally.

SettingValue
Signing certificateDistribution (App Store)
Provisioning profileApp Store provisioning profile per app
Bundle IDcom.microtec.bo / com.microtec.erp / com.microtec.vansales

For local TestFlight distribution:

  1. Open ios/<AppName>.xcworkspace in Xcode.
  2. Select the target → Signing & Capabilities.
  3. Select the correct team and provisioning profile.
  4. Product → Archive → Distribute App → App Store Connect.

CI/CD Pipeline

Each mobile app has an Azure DevOps pipeline. The pipelines are defined in Devops/azure/pipelines/mobile/.

Pipeline stages

Branch-to-environment mapping

| Branch | Environment | Distribution target | |--------|-------------|--------------------|- | develop / feature/* | dev | Firebase App Distribution (internal testers) | | stage / staging | stage | Firebase App Distribution (QA team) | | main / master | production | Google Play Store (internal track) |

Pipeline variables

The pipeline reads environment-specific variables from Azure Key Vault:

VariableDescription
ANDROID_KEYSTORE_BASE64Base64-encoded keystore file
ANDROID_KEY_ALIASKey alias
ANDROID_KEY_PASSWORDKey password
ANDROID_STORE_PASSWORDKeystore password
API_BASE_URLBackend gateway URL for the target environment
KEYCLOAK_URLKeycloak URL for the target environment
FIREBASE_APP_IDFirebase App Distribution app ID
FIREBASE_TOKENFirebase CLI authentication token

Version Numbering

Version numbers follow MAJOR.MINOR.PATCH+BUILD (e.g., 1.4.2+47):

  • MAJOR.MINOR.PATCH — semantic version, updated manually for releases
  • BUILD — auto-incremented by the CI pipeline using the Azure DevOps build number

In pubspec.yaml:

yaml
version: 1.4.2+47

The +BUILD number is the Android versionCode and iOS CFBundleVersion. It must always increase between releases.

The CI pipeline overrides the build number automatically:

bash
flutter build appbundle \
  --build-number=$(Build.BuildId) \
  --build-name=1.4.2 \
  --dart-define=API_BASE_URL=$(API_BASE_URL) \
  --dart-define=KEYCLOAK_URL=$(KEYCLOAK_URL)

Code Generation

Some packages in MobileAPIClients use build_runner for code generation (JSON serialisation, Drift database schema). The melos run init command runs code generation automatically. If you add a new model or modify the Drift schema, re-run generation manually:

bash
# Inside the package that has the models
flutter pub run build_runner build --delete-conflicting-outputs

Troubleshooting

APK build fails — JAVA_HOME not set

bash
export JAVA_HOME=$(/usr/libexec/java_home -v 17)

The Android build requires JDK 17. Ensure JAVA_HOME points to JDK 17, not a JRE.

Execution failed for task ':app:processReleaseGoogleServices'

google-services.json is missing from android/app/. This file contains the Firebase project config for push notifications. Obtain it from the Firebase Console and place it there. It is excluded from the repository — the CI pipeline injects it from Key Vault.

melos run init fails with submodule errors

bash
git submodule update --init --recursive
melos run init

The submodule was not initialised. Run git submodule update first.

IPA archive fails — No valid signing identity

  1. Open Keychain Access and verify the distribution certificate is installed.
  2. In Xcode, go to Preferences → Accounts → Download Manual Profiles.
  3. Ensure the provisioning profile in ios/ExportOptions.plist matches the installed profile.

MissingPluginException at runtime

A Flutter plugin added to pubspec.yaml requires a native rebuild. Run:

bash
flutter clean
melos run init

Internal Documentation — Microtec Platform Team