1. Pre-Deployment Checklist

1.1 Code Optimization

1.2 Security Hardening

PlatformActionImplementation Details
AndroidObfuscate with R8/ProGuardAdd -keepattributes SourceFile,LineNumberTable to preserve crash reporting
iOSEnable app transport security (ATS)Add NSAllowsArbitraryLoads: false in Info.plist
WebUse HTTPS and CSP headersSet Content-Security-Policy: default-src 'self' in server config
DesktopSign binaries with digital certificatesUse platform-specific signing tools (signtool.exe, codesign)

Additional Security Measures:

1.3 Testing Protocol

  1. 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 } }
  2. 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() } }
  3. 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 versionsfastlane pilot distribute --ipa "iosApp.ipa" --groups "Internal Testers"
    • Cross-Platform Consistency: Create a test matrix to verify feature parity across platforms

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  

Important: Store this keystore securely – losing it means you cannot update your app!

Best Practices for Keystore Management:

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
    }  
}  

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

  1. 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
  2. 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
  3. 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:


3. iOS Deployment to App Store

3.1 Xcode Archive Workflow

  1. 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>
  2. 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

  1. 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
  2. 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)
  3. 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  

Output: Static files in build/distributions including:

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()
    }
}

4.2 Hosting & Optimization

HostAdvantageDeploy CommandAdditional Setup
VercelAutomatic CDN, SSR supportvercel deploy --prodCreate vercel.json for routing
GitHub PagesFree hosting for OSS projectsgit push origin gh-pagesSet up GitHub Actions workflow
FirebaseIntegrated analytics & authfirebase deploy --only hostingConfigure firebase.json
NetlifyContinuous deployment, formsnetlify deploy --prodCreate netlify.toml
AWS S3/CloudFrontScalable, global CDNaws s3 sync build/distributions s3://bucket-nameSet up CloudFront distribution

Performance Tips:

Deployment Verification:


5. Desktop Deployment (Windows/macOS/Linux)

5.1 Package Generation

# Windows  
./gradlew :desktopApp:packageMsi  

# macOS  
./gradlew :desktopApp:packageDmg  

# Linux  
./gradlew :desktopApp:packageDeb  

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"
            }
        }
    }
}

5.2 Distribution Channels

5.3 Desktop-Specific Considerations

Platform Integration:

Update Mechanisms:


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 }}

6.2 Cost Optimization and Performance

6.3 Monitoring and Alerting

Release Health Monitoring:


7. Post-Deployment Best Practices

7.1 Version Management

Semantic Versioning:

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"
    }
}

7.2 Phased Rollouts

Android Staged Rollout:

iOS Phased Release:

Web Canary Deployments:

# Firebase hosting configuration
hosting:
  target: production
  releases:
    production:
      - version: "1.0.0"
        percentage: 10
      - version: "0.9.0"
        percentage: 90

7.3 Rollback Strategies

Emergency Rollback Plan:

  1. Android: Halt staged rollout, revert to previous version
  2. iOS: Remove app from sale temporarily if critical
  3. Web: Instant rollback with CDN configuration
  4. 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

6. CI/CD Automation

Continuous Integration Best Practices

Parallel Testing Strategy:

Caching Dependencies:

- name: Cache Gradle packages
  uses: actions/cache@v3
  with:
    path: |
      ~/.gradle/caches
      ~/.gradle/wrapper
      ~/.konan
    key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}

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)"
        }
    }
}

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])
    }
}

Key Metrics to Track:

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>)
}

Automated Alerts:


8. App Store Optimization (ASO)

8.1 Google Play Optimization

Metadata Best Practices:

Screenshot Guidelines:

Release Notes Strategy:

8.2 App Store Optimization

iOS-Specific ASO:

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()
    }
}

A/B Testing Store Listings:


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"
    }
}

Version Tracking:

9.2 Phased Rollouts

Android Staged Rollout:

iOS Phased Release:

Web Progressive Deployment:

9.3 Hotfix Strategy

Emergency Release Process:

  1. Create hotfix branch from production tag
  2. Fix critical issue with minimal changes
  3. Test thoroughly on all platforms
  4. Deploy with expedited review request (if applicable)
  5. Monitor closely after release

Rollback Plan:


10.1 Privacy Policies

Cross-Platform Requirements:

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

10.2 Terms of Service

Key Components:

Versioning Terms:

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)
    }
}

Accessibility Testing Checklist:


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:

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

External Links

For ongoing maintenance, monitor Kotlin Releases and update dependencies quarterly. 🚀

0 0 votes
Article Rating

Leave a Reply

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x