Build & Release
This page covers how to build release artifacts and submit them to the App Store (iOS) and Google Play (Android).
Versioning
Version numbers are managed in two places and must be kept in sync:
Android — composeApp/build.gradle.kts:
android {
defaultConfig {
versionCode = 135 // Integer, increment on every store submission
versionName = "2.0.0" // Semantic version shown to users
}
}
iOS — iosApp/Configuration/config.xcconfig:
MARKETING_VERSION = 2.0.0 // Displayed version (CFBundleShortVersionString)
CURRENT_PROJECT_VERSION = 135 // Build number (CFBundleVersion)
versionCode (Android) and CURRENT_PROJECT_VERSION (iOS) must be incremented for every upload to the stores, even if the user-facing version number (versionName / MARKETING_VERSION) does not change.
Android — Google Play
1. Configure release signing
Create a production keystore if you don't have one:
keytool -genkey -v \
-keystore release.keystore \
-alias association-app \
-keyalg RSA \
-keysize 2048 \
-validity 10000
Add signing config to composeApp/build.gradle.kts:
android {
signingConfigs {
create("release") {
storeFile = file("../release.keystore")
storePassword = System.getenv("KEYSTORE_PASSWORD")
keyAlias = "association-app"
keyPassword = System.getenv("KEY_PASSWORD")
}
}
buildTypes {
release {
signingConfig = signingConfigs.getByName("release")
isMinifyEnabled = true // Enable for production
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
}
The keystore file and passwords must be kept secret. Never commit the keystore or plaintext passwords to version control. Use environment variables or a secrets manager.
2. Pre-release checklist
Before building a release APK or AAB:
- Remove
android:usesCleartextTraffic="true"fromAndroidManifest.xml(development only) - Set
AppConfig.API_BASE_URLto the production API URL - Set
AppConfig.USE_MOCK_DATA = false - Increment
versionCodeinbuild.gradle.kts - Update
versionNameif this is a new user-facing version - Verify
google-services.jsonreferences the production Firebase project - Confirm Firebase Crashlytics and Analytics are enabled
3. Build the release AAB (recommended)
Google Play prefers the Android App Bundle (AAB) format:
./gradlew :composeApp:bundleRelease
Output: composeApp/build/outputs/bundle/release/composeApp-release.aab
To build a signed APK instead:
./gradlew :composeApp:assembleRelease
Output: composeApp/build/outputs/apk/release/composeApp-release.apk
4. Upload to Google Play
- Open the Google Play Console
- Select the Association App application
- Go to Release → Production (or the appropriate track: Internal, Alpha, Beta)
- Create a new release and upload the
.aabfile - Complete the release notes and submit for review
iOS — App Store
1. Pre-release checklist
- Set
AppConfig.API_BASE_URLto the production API URL - Set
AppConfig.USE_MOCK_DATA = false - Increment
CURRENT_PROJECT_VERSIONinconfig.xcconfig - Update
MARKETING_VERSIONif this is a new user-facing version - Verify
GoogleService-Info.plistreferences the production Firebase project - Confirm the correct Team and Bundle ID are set in Signing & Capabilities
- Test on a physical device (push notifications, deep links, payment flows)
2. Archive in Xcode
- Open
iosApp/iosApp.xcodeprojin Xcode - Select Any iOS Device (arm64) as the build target (not a simulator)
- Go to Product → Archive
- Wait for the archive to complete — the Organizer window opens automatically
3. Distribute via App Store Connect
In the Organizer:
- Select the latest archive
- Click Distribute App
- Choose App Store Connect
- Select Upload (to submit directly) or Export (to get a local IPA for manual upload)
- Follow the wizard — Xcode handles code signing and dSYM upload automatically
After upload, open App Store Connect:
- Go to the Association App page → TestFlight or App Store tab
- Select the new build
- Complete review information and submit for Apple review
4. dSYM upload (Kotzilla)
dSYM files enable human-readable crash reports in Kotzilla. An Xcode Run Script build phase automatically uploads dSYMs after every archive:
# Already configured in Xcode build phases
# Uploads dSYM to Kotzilla for symbolication
No manual action is needed. Verify the script is present in Build Phases if crash reports show unsymbolicated addresses.
Firebase Configuration per Environment
The project uses separate Firebase projects per environment. Swap the config files when building for different targets:
| Environment | Android | iOS |
|---|---|---|
| Development | google-services-dev.json | GoogleService-Info-dev.plist |
| Staging | google-services-staging.json | GoogleService-Info-staging.plist |
| Production | google-services.json | GoogleService-Info.plist |
Place the correct file at the expected location before building:
# Android
cp config/google-services-prod.json composeApp/google-services.json
# iOS
cp config/GoogleService-Info-prod.plist iosApp/iosApp/GoogleService-Info.plist
App Icon Variants
Dynamic branding (alternate app icons) requires each icon variant to be registered at build time in both platforms — adding a new organisation icon means:
Android:
- Add icon resources to
composeApp/src/androidMain/res/mipmap-*/ - Add a new
<activity-alias>inAndroidManifest.xml - Register the variant in
AppIconVariant.kt
iOS:
- Add an icon asset set to
iosApp/iosApp/Assets.xcassets - Add the asset name to the
ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMESbuild setting in Xcode - Register the variant in
AppIconVariant.kt
A new build and store submission is required whenever icon variants change.
Recommended Release Process
There is no CI/CD pipeline in place yet. The recommended manual process is:
1. Merge feature branches → main
2. Bump versionCode + versionName / MARKETING_VERSION + CURRENT_PROJECT_VERSION
3. Update API_BASE_URL to production
4. Swap production Firebase config files
5. Android: ./gradlew :composeApp:bundleRelease → upload AAB to Play Console
6. iOS: Xcode Archive → Distribute to App Store Connect
7. Submit both for review
8. Tag the release in git: git tag v2.1.0
Build Variants
Android build types
| Type | API URL | Minification | Cleartext traffic | Signing |
|---|---|---|---|---|
debug | api-dev.* | Off | On | Debug keystore |
release | api.* (prod) | Recommended on | Off | Release keystore |
Gradle properties (performance)
gradle.properties is already configured for optimal build performance:
org.gradle.caching=true
org.gradle.parallel=true
org.gradle.jvmargs=-Xmx4g -XX:+UseParallelGC
kotlin.incremental=true
Increase -Xmx if builds fail with out-of-memory errors on machines with more RAM available.