Appearance
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 testEnvironment Flavors
Each app supports three environments via Flutter --dart-define variables:
| Environment | API_BASE_URL | KEYCLOAK_URL | Purpose |
|---|---|---|---|
| dev | https://gateway.microtec-test.com | https://keycloak.microtec-test.com | Development and feature testing |
| stage | https://gateway.microtecstage.com | https://keycloak.microtecstage.com | Staging / UAT |
| production | https://gateway.onlinemicrotec.com.sa | https://keycloak.onlinemicrotec.com.sa | Live 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.comOutput: 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.saFor a signed release APK, the keystore must be configured first (see Android Signing below).
Android App Bundle (recommended for Play Store)
bash
flutter build appbundle --release \
--dart-define=API_BASE_URL=https://gateway.onlinemicrotec.com.sa \
--dart-define=KEYCLOAK_URL=https://keycloak.onlinemicrotec.com.saOutput: 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.plistOutput: 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.
| Setting | Value |
|---|---|
| Signing certificate | Distribution (App Store) |
| Provisioning profile | App Store provisioning profile per app |
| Bundle ID | com.microtec.bo / com.microtec.erp / com.microtec.vansales |
For local TestFlight distribution:
- Open
ios/<AppName>.xcworkspacein Xcode. - Select the target → Signing & Capabilities.
- Select the correct team and provisioning profile.
- 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:
| Variable | Description |
|---|---|
ANDROID_KEYSTORE_BASE64 | Base64-encoded keystore file |
ANDROID_KEY_ALIAS | Key alias |
ANDROID_KEY_PASSWORD | Key password |
ANDROID_STORE_PASSWORD | Keystore password |
API_BASE_URL | Backend gateway URL for the target environment |
KEYCLOAK_URL | Keycloak URL for the target environment |
FIREBASE_APP_ID | Firebase App Distribution app ID |
FIREBASE_TOKEN | Firebase 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 releasesBUILD— auto-incremented by the CI pipeline using the Azure DevOps build number
In pubspec.yaml:
yaml
version: 1.4.2+47The +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-outputsTroubleshooting
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 initThe submodule was not initialised. Run git submodule update first.
IPA archive fails — No valid signing identity
- Open Keychain Access and verify the distribution certificate is installed.
- In Xcode, go to Preferences → Accounts → Download Manual Profiles.
- Ensure the provisioning profile in
ios/ExportOptions.plistmatches the installed profile.
MissingPluginException at runtime
A Flutter plugin added to pubspec.yaml requires a native rebuild. Run:
bash
flutter clean
melos run init