1. Pre-Deployment Checklist
1.1 Code Optimization
- Minification:
android { buildTypes { release { isMinifyEnabled = true proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") // Add custom ProGuard rules for KMP libraries proguardFile("multiplatform-proguard-rules.pro") } } }
- Resource Shrinking: Remove unused assets with
shrinkResources true
in your Android build configuration. - Binary Size Analysis: Use the Android App Bundle Explorer to identify large dependencies:
bundletool build-apks --bundle=app.aab --output=app.apks bundletool get-size total --apks=app.apks
- JavaScript Optimization: For web targets, enable dead code elimination:
kotlin { js(IR) { browser { webpackTask { output.libraryTarget = "umd" // Enable tree shaking mode = org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig.Mode.PRODUCTION } } } }
1.2 Security Hardening
Platform | Action | Implementation Details |
---|---|---|
Android | Obfuscate with R8/ProGuard | Add -keepattributes SourceFile,LineNumberTable to preserve crash reporting |
iOS | Enable app transport security (ATS) | Add NSAllowsArbitraryLoads: false in Info.plist |
Web | Use HTTPS and CSP headers | Set Content-Security-Policy: default-src 'self' in server config |
Desktop | Sign binaries with digital certificates | Use platform-specific signing tools (signtool.exe, codesign) |
Additional Security Measures:
- Certificate Pinning: Implement SSL pinning for API communications
// In shared code expect fun setupCertificatePinning(host: String, pins: List<String>) // Android implementation actual fun setupCertificatePinning(host: String, pins: List<String>) { OkHttpClient.Builder() .certificatePinner(CertificatePinner.Builder() .add(host, *pins.toTypedArray()) .build()) .build() }
- Secure Storage: Use encrypted storage for sensitive data
- API Key Protection: Never hardcode API keys; use BuildConfig fields or platform-specific secure storage
1.3 Testing Protocol
- Unit Tests:
./gradlew allTests
Ensure test coverage across all platforms with platform-specific test configurations:kotlin { sourceSets { val commonTest by getting { dependencies { implementation(kotlin("test")) implementation("io.kotest:kotest-assertions-core:5.6.2") } } val androidUnitTest by getting { dependencies { implementation("junit:junit:4.13.2") implementation("androidx.test:runner:1.5.2") } } val iosTest by getting } }
- UI Tests:
class LoginScreenTest { @Test fun invalidLoginShowsError() { composeTestRule.setContent { AppTheme { LoginScreen() } } // Input invalid credentials composeTestRule.onNodeWithTag("email_field").performTextInput("invalid@email") composeTestRule.onNodeWithTag("password_field").performTextInput("short") composeTestRule.onNodeWithTag("login_button").performClick() // Verify error is displayed composeTestRule.onNodeWithText("Invalid credentials").assertIsDisplayed() } }
- Real Device Testing:
- Android: Use Firebase Test Lab with a variety of device configurations
./gradlew assembleDebug gcloud firebase test android run --type instrumentation \ --app app/build/outputs/apk/debug/app-debug.apk \ --test app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk \ --device model=Pixel6,version=33,locale=en,orientation=portrait
- iOS: Deploy to TestFlight for real device testing across different iOS versions
fastlane pilot distribute --ipa "iosApp.ipa" --groups "Internal Testers"
- Cross-Platform Consistency: Create a test matrix to verify feature parity across platforms
- Android: Use Firebase Test Lab with a variety of device configurations
2. Android Deployment to Google Play
2.1 Generate Signed Bundle
Step 1: Create Keystore
keytool -genkeypair -v -keystore release.jks -keyalg RSA -keysize 4096 -validity 10000 -alias my-app
Code language: CSS (css)
Important: Store this keystore securely – losing it means you cannot update your app!
Best Practices for Keystore Management:
- Store in a secure password manager or company vault
- Create backup copies in secure locations
- Document the keystore password and key alias in a secure location
- Consider using Google Play App Signing for additional security
Step 2: Configure Gradle
signingConfigs {
create("release") {
storeFile = rootProject.file("release.jks")
// Don't hardcode passwords in build files
storePassword = System.getenv("STORE_PASSWORD") ?: properties["STORE_PASSWORD"].toString()
keyAlias = "my-app"
keyPassword = System.getenv("KEY_PASSWORD") ?: properties["KEY_PASSWORD"].toString()
// Enable v1 and v2 signing for maximum compatibility
enableV1Signing = true
enableV2Signing = true
}
}
buildTypes {
release {
signingConfig = signingConfigs["release"]
// Additional release optimizations
isShrinkResources = true
isMinifyEnabled = true
}
}
Code language: PHP (php)
Step 3: Build AAB
./gradlew :androidApp:bundleRelease
The AAB file will be located at androidApp/build/outputs/bundle/release/androidApp-release.aab
Verify Bundle Contents:
bundletool build-apks --bundle=androidApp-release.aab --output=androidApp.apks
bundletool extract-apks --apks=androidApp.apks --output-dir=extracted --device-spec=device-spec.json
2.2 Play Store Submission
- Create Store Listing:
- Comply with Store Policy
- Localize metadata for target regions using Play Console’s translation service
- Complete all required fields:
- App name (30 characters max)
- Short description (80 characters max)
- Full description (4000 characters max)
- App category and tags
- Content rating questionnaire
- Contact details and privacy policy URL
- Upload Assets:
- Icon: 512×512 PNG with transparency
- Feature Graphic: 1024×500 JPG or PNG (no transparency)
- Screenshots:
- Phone: At least 2 screenshots (16:9 aspect ratio recommended)
- 7″ tablet: At least 2 screenshots (16:9 aspect ratio)
- 10″ tablet: At least 2 screenshots (16:9 aspect ratio)
- Video: YouTube URL with public or unlisted visibility
- Release Tracks:
- Internal testing: Limited to 100 testers for quick feedback
- Closed testing: Alpha/Beta tracks for larger testing groups
- Open testing: Public beta with Play Store visibility
- Production: Staged rollout (start with 10%, then 25%, 50%, 100%)
Play Store Optimization Tips:
- Use A/B testing for store listings to optimize conversion
- Monitor Android vitals for crash reports and ANRs
- Respond to user reviews promptly
- Update screenshots with each major UI change
3. iOS Deployment to App Store
3.1 Xcode Archive Workflow
- Configure Signing:
- Create App ID in Apple Developer Portal
- Generate provisioning profiles:
- Development: For testing on physical devices
- Distribution: For App Store submission
- Set up automatic signing in Xcode:
// In Xcode project settings DEVELOPMENT_TEAM = "YOUR_TEAM_ID"; CODE_SIGN_STYLE = Automatic;
- Add capabilities as needed (Push Notifications, In-App Purchases, etc.)
// In Xcode project capabilities tab // Or manually in entitlements file <key>com.apple.developer.in-app-payments</key> <array> <string>merchant.com.yourcompany.app</string> </array>
- Build Archive:
# Build the Kotlin framework ./gradlew :iosApp:linkReleaseFrameworkIosArm64 # Archive the app (can also be done through Xcode UI) xcodebuild -workspace iosApp.xcworkspace -scheme iosApp -configuration Release -sdk iphoneos -archivePath build/iosApp.xcarchive archive # Export IPA xcodebuild -exportArchive -archivePath build/iosApp.xcarchive -exportOptionsPlist exportOptions.plist -exportPath build/ipa
exportOptions.plist example:<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>method</key> <string>app-store</string> <key>teamID</key> <string>YOUR_TEAM_ID</string> </dict> </plist>
3.2 App Store Connect Submission
- Prepare Metadata:
- App Information:
- App name (30 characters max)
- Subtitle (30 characters max)
- Keywords (100 characters max, comma-separated)
- Support URL
- Marketing URL (optional)
- Privacy nutrition labels: Complete App Privacy questionnaire
- Data types collected
- Tracking purposes
- Third-party data sharing
- Age ratings: Complete Age Rating (CARROT) questionnaire
- Content types
- Web access limitations
- Gambling and contests
- App Information:
- TestFlight Upload:
# Using Fastlane fastlane pilot upload --ipa "build/ipa/iosApp.ipa" --api_key_path "AuthKey.p8" --api_key_id "YOUR_KEY_ID" --issuer_id "YOUR_ISSUER_ID" # Or using Transporter app xcrun altool --upload-app -f build/ipa/iosApp.ipa -t ios -u "YOUR_APPLE_ID" -p "YOUR_APP_SPECIFIC_PASSWORD"
TestFlight Distribution:- Internal testers (up to 100 members of your team)
- External testers (up to 10,000 users via email or public link)
- Beta App Review required for external testing (1-2 day review process)
- App Review Tips:
- Provide demo account credentials in App Review Information
- Include detailed testing instructions
- Explain KMP in review notes to clarify any unusual implementation details
- Prepare for common rejection reasons:
- Crashes and bugs
- Broken links
- Incomplete information
- Misleading descriptions
- Average review time: 1-3 days (can be expedited in special circumstances)
4. Web Deployment (Kotlin/JS)
4.1 Production Build
# Generate optimized JS bundle
./gradlew jsBrowserProductionWebpack
Code language: PHP (php)
Output: Static files in build/distributions
including:
- Main JS bundle
- CSS files
- HTML entry point
- Source maps (optional)
Optimization Options:
kotlin {
js(IR) {
browser {
commonWebpackConfig {
cssSupport {
enabled.set(true)
}
// Enable code splitting
outputFileName = "app.[contenthash].js"
}
// Enable progressive web app features
webpackTask {
output.libraryTarget = "umd"
// Add service worker for offline support
output.globalObject = "this"
}
// Optimize bundle size
dceTask {
keep("app.org.example.main")
}
}
binaries.executable()
}
}
Code language: JavaScript (javascript)
4.2 Hosting & Optimization
Host | Advantage | Deploy Command | Additional Setup |
---|---|---|---|
Vercel | Automatic CDN, SSR support | vercel deploy --prod | Create vercel.json for routing |
GitHub Pages | Free hosting for OSS projects | git push origin gh-pages | Set up GitHub Actions workflow |
Firebase | Integrated analytics & auth | firebase deploy --only hosting | Configure firebase.json |
Netlify | Continuous deployment, forms | netlify deploy --prod | Create netlify.toml |
AWS S3/CloudFront | Scalable, global CDN | aws s3 sync build/distributions s3://bucket-name | Set up CloudFront distribution |
Performance Tips:
- Compression: Enable Brotli compression for smaller file sizes
# Nginx configuration brotli on; brotli_comp_level 6; brotli_types text/plain text/css application/javascript application/json;
- Caching: Set appropriate cache headers
# For static assets with content hash in filename location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|otf|eot)$ { expires 1y; add_header Cache-Control "public, max-age=31536000, immutable"; } # For HTML and other dynamic content location / { expires -1; add_header Cache-Control "no-store, no-cache, must-revalidate"; }
- Lazy Loading: Implement route-based code splitting
- Core Web Vitals: Optimize for:
- Largest Contentful Paint (LCP): < 2.5s
- First Input Delay (FID): < 100ms
- Cumulative Layout Shift (CLS): < 0.1
Deployment Verification:
- Test on multiple browsers (Chrome, Firefox, Safari, Edge)
- Verify mobile responsiveness
- Run Lighthouse audit for performance scoring
- Check console for JavaScript errors
5. Desktop Deployment (Windows/macOS/Linux)
5.1 Package Generation
# Windows
./gradlew :desktopApp:packageMsi
# macOS
./gradlew :desktopApp:packageDmg
# Linux
./gradlew :desktopApp:packageDeb
Code language: PHP (php)
Configuration Options:
compose.desktop {
application {
mainClass = "MainKt"
nativeDistributions {
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
// Windows-specific options
windows {
menuGroup = "Kotlin Multiplatform Apps"
// Add icon and installer customization
iconFile.set(project.file("icon.ico"))
upgradeUuid = "your-app-upgrade-uuid"
perUserInstall = true
}
// macOS-specific options
macOS {
bundleID = "com.example.app"
// Add signing identity for notarization
signing {
sign.set(true)
identity.set("Developer ID Application: Your Name (TEAM_ID)")
}
// Add entitlements for App Sandbox
entitlementsFile.set(project.file("entitlements.plist"))
}
// Linux-specific options
linux {
packageName = "kmp-app"
debMaintainer = "support@example.com"
appCategory = "Development"
menuGroup = "Development"
}
}
}
}
Code language: JavaScript (javascript)
5.2 Distribution Channels
- Microsoft Store:
- Convert MSI to MSIX with MSIX Packaging Tool
- Create app identity in Partner Center
- Sign package with store-provided certificate:
SignTool sign /fd SHA256 /a /f MyCertificate.pfx /p MyPassword MyApp.msix
- Submit through Partner Center dashboard
- Pass Windows App Certification Kit (WACK) tests
- Apple Notarization:
# Step 1: Create App-Specific Password in Apple ID account # Step 2: Submit for notarization xcrun notarytool submit MyApp.dmg --apple-id "your@email.com" --password "app-specific-password" --team-id "TEAM_ID" # Step 3: Check status xcrun notarytool info [REQUEST_UUID] --apple-id "your@email.com" --password "app-specific-password" --team-id "TEAM_ID" # Step 4: Staple ticket to DMG xcrun stapler staple MyApp.dmg
Notarization Requirements:- Code must be signed with Developer ID certificate
- App must use the Hardened Runtime
- App must have secure timestamp
- DMG must be signed
- Snap Store:
# snapcraft.yaml name: my-kmp-app base: core22 version: '1.0' summary: KMP Desktop Application description: | A cross-platform desktop application built with Kotlin Multiplatform and Compose for Desktop. grade: stable confinement: strict apps: my-kmp-app: command: bin/desktop extensions: [gnome] desktop: usr/share/applications/my-kmp-app.desktop parts: my-kmp-app: plugin: dump source: build/compose/binaries/main/deb stage-packages: - libgtk-3-0 - libx11-6
Publishing to Snap Store:# Login to Snap Store snapcraft login # Build snap package snapcraft # Upload to store snapcraft upload --release=stable my-kmp-app_1.0_amd64.snap
- Homebrew Cask (macOS):
# my-kmp-app.rb cask "my-kmp-app" do version "1.0.0" sha256 "calculated-sha256-of-your-dmg" url "https://github.com/yourusername/my-kmp-app/releases/download/v#{version}/MyApp-#{version}.dmg" name "My KMP App" desc "Cross-platform desktop application built with Kotlin Multiplatform" homepage "https://example.com/my-kmp-app" app "My KMP App.app" end
5.3 Desktop-Specific Considerations
Platform Integration:
- Windows:
- Register file associations in registry
- Add to Start Menu with proper categories
- Support high-DPI displays with manifest settings
- macOS:
- Support dark mode with proper color schemes
- Implement Services menu integration
- Add Spotlight metadata
- Linux:
- Create proper .desktop file with MIME types
- Support XDG standards for file locations
- Test on multiple distributions (Ubuntu, Fedora)
Update Mechanisms:
- Auto-updates:
// In shared code expect class UpdateManager { fun checkForUpdates() fun downloadUpdate() fun installUpdate() } // Platform-specific implementations actual class UpdateManager { actual fun checkForUpdates() { // Platform-specific update check } // ... }
6. CI/CD Automation
6.1 GitHub Actions Workflow
name: KMP Deploy
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
android:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v3
with:
java-version: 17
distribution: 'temurin'
cache: 'gradle'
- name: Build Android App Bundle
run: ./gradlew :androidApp:bundleRelease
- name: Sign Android App Bundle
uses: r0adkll/sign-android-release@v1
with:
releaseDirectory: androidApp/build/outputs/bundle/release
signingKeyBase64: ${{ secrets.SIGNING_KEY }}
alias: ${{ secrets.KEY_ALIAS }}
keyStorePassword: ${{ secrets.KEY_STORE_PASSWORD }}
keyPassword: ${{ secrets.KEY_PASSWORD }}
- name: Upload to Google Play
if: github.event_name != 'pull_request'
uses: r0adkll/upload-google-play@v1
with:
serviceAccountJson: ${{ secrets.GCP_SA }}
packageName: com.example.app
releaseFiles: androidApp/build/outputs/bundle/release/androidApp-release.aab
track: internal
status: completed
ios:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v3
with:
java-version: 17
distribution: 'temurin'
cache: 'gradle'
- name: Build iOS Framework
run: ./gradlew :iosApp:linkReleaseFrameworkIosArm64
- name: Install Apple Certificates
if: github.event_name != 'pull_request'
uses: apple-actions/import-codesign-certs@v1
with:
p12-file-base64: ${{ secrets.IOS_P12 }}
p12-password: ${{ secrets.IOS_P12_PASS }}
- name: Build and Archive iOS App
if: github.event_name != 'pull_request'
run: |
cd iosApp
xcodebuild -workspace iosApp.xcworkspace -scheme iosApp -configuration Release -sdk iphoneos -archivePath build/iosApp.xcarchive archive
- name: Export IPA
if: github.event_name != 'pull_request'
run: |
cd iosApp
xcodebuild -exportArchive -archivePath build/iosApp.xcarchive -exportOptionsPlist exportOptions.plist -exportPath build/ipa
- name: Upload to TestFlight
if: github.event_name != 'pull_request'
uses: apple-actions/upload-testflight-build@v1
with:
app-path: iosApp/build/ipa/iosApp.ipa
api-key-id: ${{ secrets.APPSTORE_API_KEY_ID }}
api-key-issuer-id: ${{ secrets.APPSTORE_API_KEY_ISSUER_ID }}
api-key-content: ${{ secrets.APPSTORE_API_KEY_CONTENT }}
web:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v3
with:
java-version: 17
distribution: 'temurin'
cache: 'gradle'
- name: Build Web App
run: ./gradlew jsBrowserProductionWebpack
- name: Deploy to Firebase
if: github.event_name != 'pull_request'
uses: FirebaseExtended/action-hosting-deploy@v0
with:
repoToken: ${{ secrets.GITHUB_TOKEN }}
firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT }}
channelId: live
projectId: your-firebase-project-id
desktop:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
include:
- os: ubuntu-latest
task: packageDeb
artifact: build/compose/binaries/main/deb/*.deb
- os: windows-latest
task: packageMsi
artifact: build/compose/binaries/main/msi/*.msi
- os: macos-latest
task: packageDmg
artifact: build/compose/binaries/main/dmg/*.dmg
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v3
with:
java-version: 17
distribution: 'temurin'
cache: 'gradle'
- name: Build Desktop Package
run: ./gradlew :desktopApp:${{ matrix.task }}
- name: Upload Artifact
uses: actions/upload-artifact@v3
with:
name: desktop-${{ matrix.os }}
path: ${{ matrix.artifact }}
- name: Create GitHub Release
if: github.event_name != 'pull_request' && startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@v1
with:
files: ${{ matrix.artifact }}
Code language: PHP (php)
6.2 Cost Optimization and Performance
- Caching Strategies:
- name: Cache Gradle packages uses: actions/cache@v3 with: path: | ~/.gradle/caches ~/.gradle/wrapper ~/.konan key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} restore-keys: | ${{ runner.os }}-gradle-
- Self-hosted Runners:
jobs: build: runs-on: self-hosted # Job steps...
Benefits:- Faster builds with pre-installed dependencies
- No GitHub-hosted runner minute limitations
- Custom hardware for resource-intensive builds
- Parallel Execution:
- Use matrix strategy for multiple platform builds
- Split test execution by modules or test types
- Use Gradle’s parallel execution flag:
./gradlew --parallel
6.3 Monitoring and Alerting
Release Health Monitoring:
- Firebase Crashlytics integration:
// In build.gradle.kts dependencies { implementation("com.google.firebase:firebase-crashlytics:18.4.0") }
- App Store Connect Analytics:
- Monitor crash reports and user feedback
- Track adoption rates of new versions
- Automated Alerts:
# GitHub workflow for crash alerts name: Crash Alert on: schedule: - cron: '0 */6 * * *' # Every 6 hours jobs: check-crashes: runs-on: ubuntu-latest steps: - name: Check Firebase Crashlytics uses: firebase/firebase-admin-node@v1 with: # Custom script to check crash rates script: | const crashes = await firebase.crashlytics().getCrashRate(); if (crashes > THRESHOLD) { // Send alert to Slack/Teams/Email }
7. Post-Deployment Best Practices
7.1 Version Management
Semantic Versioning:
- Major: Breaking changes (2.0.0)
- Minor: New features, backward compatible (1.1.0)
- Patch: Bug fixes (1.0.1)
Implementation:
// In build.gradle.kts
val versionMajor = 1
val versionMinor = 0
val versionPatch = 0
val versionBuild = 0 // Incremented for each build
android {
defaultConfig {
versionCode = versionMajor * 10000 + versionMinor * 1000 + versionPatch * 100 + versionBuild
versionName = "$versionMajor.$versionMinor.$versionPatch"
}
}
Code language: JavaScript (javascript)
7.2 Phased Rollouts
Android Staged Rollout:
- Start with 10% of users
- Monitor crash-free rate and ANRs
- Gradually increase to 25%, 50%, 100%
iOS Phased Release:
- Enable “Phased Release for Automatic Updates” in App Store Connect
- Automatic 7-day rollout (Day 1: 1%, Day 2: 2%, Day 3: 5%, etc.)
Web Canary Deployments:
# Firebase hosting configuration
hosting:
target: production
releases:
production:
- version: "1.0.0"
percentage: 10
- version: "0.9.0"
percentage: 90
Code language: CSS (css)
7.3 Rollback Strategies
Emergency Rollback Plan:
- Android: Halt staged rollout, revert to previous version
- iOS: Remove app from sale temporarily if critical
- Web: Instant rollback with CDN configuration
- Desktop: Provide downgrade path in auto-updater
Automated Rollback Triggers:
name: Auto-Rollback
on:
workflow_dispatch:
schedule:
- cron: '*/15 * * * *' # Check every 15 minutes
jobs:
monitor:
runs-on: ubuntu-latest
steps:
- name: Check Error Rates
Code language: PHP (php)
6. CI/CD Automation
Continuous Integration Best Practices
Parallel Testing Strategy:
- Split tests by module to reduce build times:
jobs: test: strategy: matrix: module: [shared, androidApp, iosApp, webApp] steps: - run: ./gradlew :${{ matrix.module }}:test
Caching Dependencies:
- name: Cache Gradle packages
uses: actions/cache@v3
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
~/.konan
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
Code language: PHP (php)
Environment-Specific Configurations:
// build.gradle.kts
android {
buildTypes {
create("staging") {
initWith(getByName("debug"))
buildConfigField("String", "API_URL", "\"https://staging-api.example.com\"")
manifestPlaceholders["appName"] = "MyApp (Staging)"
}
}
}
Code language: JavaScript (javascript)
7. Post-Deployment Monitoring
7.1 Analytics Integration
Cross-Platform Analytics:
// In shared code
expect class AnalyticsTracker {
fun logEvent(name: String, params: Map<String, Any>)
}
// In Android
actual class AnalyticsTracker {
private val firebaseAnalytics = FirebaseAnalytics.getInstance(context)
actual fun logEvent(name: String, params: Map<String, Any>) {
val bundle = Bundle()
params.forEach { (key, value) ->
when (value) {
is String -> bundle.putString(key, value)
is Int -> bundle.putInt(key, value)
// Handle other types
}
}
firebaseAnalytics.logEvent(name, bundle)
}
}
// In iOS
actual class AnalyticsTracker {
actual fun logEvent(name: String, params: Map<String, Any>) {
Analytics.logEvent(name, parameters: params as? [String: NSObject])
}
}
Code language: JavaScript (javascript)
Key Metrics to Track:
- Installation Success Rate: Track failed installations vs successful ones
- Crash-Free Users: Percentage of users who don’t experience crashes
- Feature Adoption: Usage of new features after deployment
- Performance Metrics: App startup time, screen load times, memory usage
7.2 Crash Reporting
Unified Error Handling:
// In shared code
fun reportException(exception: Throwable, metadata: Map<String, String> = emptyMap()) {
try {
platformCrashReporter.reportException(exception, metadata)
} catch (e: Exception) {
// Fallback logging
println("Failed to report exception: $exception")
}
}
// Platform-specific implementations
expect object platformCrashReporter {
fun reportException(exception: Throwable, metadata: Map<String, String>)
}
Code language: JavaScript (javascript)
Automated Alerts:
- Set up Slack/Teams notifications for critical crashes
- Create severity-based alerting thresholds
- Implement on-call rotation for production issues
8. App Store Optimization (ASO)
8.1 Google Play Optimization
Metadata Best Practices:
- Title: Include primary keyword (49 characters max)
- Short Description: Focus on benefits, not features (80 characters)
- Long Description: Include all relevant keywords naturally (4000 characters)
- Promotional Text: Update regularly without app submission
Screenshot Guidelines:
- Show key features in first 3 screenshots
- Include text overlays explaining benefits
- Localize screenshots for top markets
- Use device frames for context
Release Notes Strategy:
- Highlight new features prominently
- Address fixed issues users reported
- Keep tone consistent with app branding
- Include call-to-action for ratings
8.2 App Store Optimization
iOS-Specific ASO:
- App Name: 30 characters including primary keyword
- Subtitle: 30 characters with secondary keywords
- Keywords Field: 100 characters of comma-separated keywords (no spaces)
- Promotional Text: 170 characters that can be updated without review
Review Prompt Implementation:
// In iOS app
func requestReview() {
if #available(iOS 14.0, *) {
if let scene = UIApplication.shared.connectedScenes.first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene {
SKStoreReviewController.requestReview(in: scene)
}
} else {
SKStoreReviewController.requestReview()
}
}
Code language: PHP (php)
A/B Testing Store Listings:
- Use Google Play Experiments for Android
- Test icon variations, screenshots, and descriptions
- Run tests for at least 7 days with significant traffic
9. Versioning and Updates
9.1 Semantic Versioning
Version Scheme:
// In build.gradle.kts
val versionMajor = 1
val versionMinor = 2
val versionPatch = 3
val versionBuild = 45
android {
defaultConfig {
versionCode = versionMajor * 10000 + versionMinor * 1000 + versionPatch * 100 + versionBuild
versionName = "$versionMajor.$versionMinor.$versionPatch"
}
}
Code language: JavaScript (javascript)
Version Tracking:
- Use git tags for release versions
- Automate version bumping in CI/CD
- Include build number for internal tracking
9.2 Phased Rollouts
Android Staged Rollout:
- Start with 5-10% of users
- Monitor crash-free rate for 24-48 hours
- Increase to 25%, then 50%, then 100% if stable
iOS Phased Release:
- Enable “Phased Release for Automatic Updates”
- Monitor App Store Connect metrics daily
- Be prepared to halt rollout if issues arise
Web Progressive Deployment:
- Use feature flags for new functionality
- Deploy to staging environment first
- Use canary deployments for high-risk changes
9.3 Hotfix Strategy
Emergency Release Process:
- Create hotfix branch from production tag
- Fix critical issue with minimal changes
- Test thoroughly on all platforms
- Deploy with expedited review request (if applicable)
- Monitor closely after release
Rollback Plan:
- Document exact steps for reverting to previous version
- Maintain previous version infrastructure
- Practice rollback procedures regularly
10. Legal and Compliance
10.1 Privacy Policies
Cross-Platform Requirements:
- GDPR compliance for European users
- CCPA compliance for California residents
- App-specific privacy policies for each store
Implementation Example:
// In shared code
@Composable
fun PrivacyPolicyScreen() {
val privacyPolicyUrl = rememberPlatformPrivacyPolicyUrl()
Column(modifier = Modifier.padding(16.dp)) {
Text("Privacy Policy", style = MaterialTheme.typography.h5)
Spacer(Modifier.height(16.dp))
Text("Please review our privacy policy to understand how we collect and use your data.")
Spacer(Modifier.height(16.dp))
Button(onClick = { openUrl(privacyPolicyUrl) }) {
Text("View Privacy Policy")
}
}
}
// Platform-specific implementation
expect fun rememberPlatformPrivacyPolicyUrl(): String
Code language: JavaScript (javascript)
10.2 Terms of Service
Key Components:
- User rights and responsibilities
- Intellectual property protection
- Limitation of liability
- Dispute resolution process
Versioning Terms:
- Track user acceptance of terms
- Prompt for re-acceptance after significant changes
- Store acceptance records securely
10.3 Accessibility Compliance
Cross-Platform Accessibility:
// In shared code
@Composable
fun AccessibleButton(
onClick: () -> Unit,
text: String,
contentDescription: String? = null
) {
Button(
onClick = onClick,
modifier = Modifier.semantics {
if (contentDescription != null) {
this.contentDescription = contentDescription
}
}
) {
Text(text)
}
}
Code language: JavaScript (javascript)
Accessibility Testing Checklist:
- Screen reader compatibility
- Sufficient color contrast (WCAG AA/AAA)
- Touch target size (at least 44×44 dp)
- Keyboard navigation support
Conclusion
Deploying Kotlin Multiplatform applications requires careful planning and platform-specific knowledge, but the benefits of code sharing and unified deployment processes make it worthwhile. By following this comprehensive guide, you can streamline your deployment workflow, ensure compliance with platform requirements, and deliver high-quality applications to users across Android, iOS, web, and desktop platforms.
Remember that deployment is not the end of the development cycle but rather an ongoing process of monitoring, optimization, and improvement. Regular updates, responsive customer support, and continuous performance monitoring will help ensure your KMP application’s long-term success.
Additional Resources:
- Official Kotlin Multiplatform Documentation
- Google Play Console Help
- App Store Connect Help
- Firebase App Distribution
- GitHub Actions Documentation
This continuation completes the deployment guide with detailed sections on CI/CD best practices, post-deployment monitoring, app store optimization, versioning strategies, and legal compliance considerations. Each section includes practical code examples, configuration snippets, and actionable advice for Kotlin Multiplatform developers.
Internal Links
- A Comprehensive Guide to Cross-Platform Development
- How to Switch Ruby Versions on Mac (M1, M2, M3, M4 Guide)
- Mastering Lifecycle & Performance in Compose Multiplatform | Cross-Platform Optimization Guide
External Links
For ongoing maintenance, monitor Kotlin Releases and update dependencies quarterly. 🚀