Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

The rollercoaster of releasing an Android, iOS,...

The rollercoaster of releasing an Android, iOS, and macOS app with Kotlin Multiplatform | droidcon Berlin

With the rise of Kotlin Multiplatform, the possibility of expanding to multiple platforms has increased, especially for Android Developers. It's easier than before to build for other platforms.

But how to release your app to multiple platforms?

In this talk, I will share all the things I've learned around distributing FeedFlow, an Android, iOS, and macOS app built with Kotlin Multiplatform, coming from an Android development background.

We will cover the deployment of the binary, automating everything with CI, crash reporting, logging, internationalization, and all you need to know to successfully distribute your KMP app.

Marco Gomiero

July 04, 2024
Tweet

More Decks by Marco Gomiero

Other Decks in Programming

Transcript

  1. Marco Gomiero @marcoGomier Senior Android Developer @ Airalo Google Developer

    Expert for Kotlin The rollercoaster of releasing an Android, iOS, and macOS app with Kotlin Multiplatform
  2. @marcoGomier • Sign the app • Provisioning profiles • Upload

    to TestFlight • All handled by Xcode (or manually on the CI) iOS
  3. @marcoGomier • Sign the app • Provisioning profiles • Upload

    to TestFlight • All handled by Xcode (or manually on the CI) iOS
  4. @marcoGomier Notarization $ xcrun notarytool submit $BINARY_PATH --apple-id $APPLE_ID_NOTARIZATION --password

    $NOTARIZATION_PWD --team-id $APPSTORE_TEAM_ID --wait $ xcrun stapler staple $BINARY_PATH
  5. @marcoGomier macOS - App Store • Sign the app •

    Provisioning profiles • Upload to TestFlight
  6. @marcoGomier Native libraries on JVM • Native libraries are embedded

    in the dependencies jar • When needed, they are extracted and loaded
  7. @marcoGomier val isSandboxed = System.getenv("APP_SANDBOX_CONTAINER_ID") != null if (isSandboxed) {

    val resourcesPath = System.getProperty("compose.application.resources.dir") // sqlite-jdbc System.setProperty("org.sqlite.lib.path", resourcesPath) }
  8. @marcoGomier val logger = Logger( config = StaticConfig( logWriterList =

    listOf( platformLogWriter(), crashReportingLogWriter(), ), ), tag = "FeedFlow", )
  9. @marcoGomier val logger = Logger( config = StaticConfig( logWriterList =

    listOf( platformLogWriter(), crashReportingLogWriter(), ), ), tag = "FeedFlow", )
  10. @marcoGomier val logger = Logger( config = StaticConfig( logWriterList =

    listOf( platformLogWriter(), crashReportingLogWriter(), ), ), tag = "FeedFlow", )
  11. @marcoGomier internal expect fun crashReportingLogWriter(): LogWriter actual fun crashReportingLogWriter(): LogWriter

    = CrashlyticsLogWriter() actual fun crashReportingLogWriter(): LogWriter = SentryLogWriter()
  12. @marcoGomier class SentryLogWriter : LogWriter() { override fun isLoggable(tag: String,

    severity: Severity): Boolean { // .. } override fun log( severity: Severity, message: String, tag: String, throwable: Throwable? ) { // .. } }
  13. @marcoGomier val feedFlowStrings: Map<String, FeedFlowStrings> = mapOf( Locales.It to ItFeedFlowStrings,

    Locales.En to EnFeedFlowStrings, Locales.De to DeFeedFlowStrings, )
  14. @marcoGomier val feedFlowStrings: Map<String, FeedFlowStrings> = mapOf( Locales.It to ItFeedFlowStrings,

    Locales.En to EnFeedFlowStrings, Locales.De to DeFeedFlowStrings, ) FeedFlowTheme { val lyricist = rememberFeedFlowStrings() ProvideFeedFlowStrings(lyricist) { MyAppEntrypoint() } }
  15. @marcoGomier val feedFlowStrings: Map<String, FeedFlowStrings> = mapOf( Locales.It to ItFeedFlowStrings,

    Locales.En to EnFeedFlowStrings, Locales.De to DeFeedFlowStrings, ) FeedFlowTheme { val lyricist = rememberFeedFlowStrings() ProvideFeedFlowStrings(lyricist) { MyAppEntrypoint() } } Text(text = LocalFeedFlowStrings.current.feedUrl)
  16. @marcoGomier val feedFlowStrings: Map<String, FeedFlowStrings> = mapOf( Locales.It to ItFeedFlowStrings,

    Locales.En to EnFeedFlowStrings, Locales.De to DeFeedFlowStrings, )
  17. @marcoGomier val feedFlowStrings: Map<String, FeedFlowStrings> = mapOf( Locales.It to ItFeedFlowStrings,

    Locales.En to EnFeedFlowStrings, Locales.De to DeFeedFlowStrings, ) func startKoin() { let langCode = Locale.current.language.languageCode?.identifier ?? "en" let koinApplication = KoinIOSKt.doInitKoinIos(languageCode: langCode) _feedFlowStrings = KotlinDependencies.shared.getFeedFlowStrings() } private var _feedFlowStrings: FeedFlowStrings? var feedFlowStrings: FeedFlowStrings { return _feedFlowStrings! }
  18. @marcoGomier val feedFlowStrings: Map<String, FeedFlowStrings> = mapOf( Locales.It to ItFeedFlowStrings,

    Locales.En to EnFeedFlowStrings, Locales.De to DeFeedFlowStrings, ) func startKoin() { let langCode = Locale.current.language.languageCode?.identifier ?? "en" let koinApplication = KoinIOSKt.doInitKoinIos(languageCode: langCode) _feedFlowStrings = KotlinDependencies.shared.getFeedFlowStrings() } private var _feedFlowStrings: FeedFlowStrings? var feedFlowStrings: FeedFlowStrings { return _feedFlowStrings! }
  19. @marcoGomier val feedFlowStrings: Map<String, FeedFlowStrings> = mapOf( Locales.It to ItFeedFlowStrings,

    Locales.En to EnFeedFlowStrings, Locales.De to DeFeedFlowStrings, ) func startKoin() { let langCode = Locale.current.language.languageCode?.identifier ?? "en" let koinApplication = KoinIOSKt.doInitKoinIos(languageCode: langCode) _feedFlowStrings = KotlinDependencies.shared.getFeedFlowStrings() single<FeedFlowStrings> { feedFlowStrings[languageCode] ?: EnFeedFlowStrings }
  20. @marcoGomier val feedFlowStrings: Map<String, FeedFlowStrings> = mapOf( Locales.It to ItFeedFlowStrings,

    Locales.En to EnFeedFlowStrings, Locales.De to DeFeedFlowStrings, ) func startKoin() { let langCode = Locale.current.language.languageCode?.identifier ?? "en" let koinApplication = KoinIOSKt.doInitKoinIos(languageCode: langCode) _feedFlowStrings = KotlinDependencies.shared.getFeedFlowStrings() } private var _feedFlowStrings: FeedFlowStrings? var feedFlowStrings: FeedFlowStrings { return _feedFlowStrings! }
  21. @marcoGomier val feedFlowStrings: Map<String, FeedFlowStrings> = mapOf( Locales.It to ItFeedFlowStrings,

    Locales.En to EnFeedFlowStrings, Locales.De to DeFeedFlowStrings, ) func startKoin() { let langCode = Locale.current.language.languageCode?.identifier ?? "en" let koinApplication = KoinIOSKt.doInitKoinIos(languageCode: langCode) _feedFlowStrings = KotlinDependencies.shared.getFeedFlowStrings() Text(feedFlowStrings.searchHintTitle)
  22. @marcoGomier Conclusions • Start from things you already know •

    Go incrementally • Exploring other planets enriches your vision
  23. @marcoGomier Thank you! > Twitter: @marcoGomier > Github: prof18 >

    Website: marcogomiero.com > Mastodon: androiddev.social/@marcogom Marco Gomiero Senior Android Developer @ Airalo Google Developer Expert for Kotlin
  24. Bibliography / Useful Links • https: / / feedflow.dev/ •

    https: / / developer.apple.com/documentation/security/notarizing_macos_software_before_distribution • https: / / help.apple.com/itc/transporteruserguide/en.lproj/static.html#apd70774093eddb4 • https: / / developer.apple.com/documentation/security/app_sandbox/protecting_user_data_with_app_sandbox • https: / / github.com/JetBrains/compose-multiplatform/blob/master/tutorials/Native_distributions_and_local_execution/README.md#adding-files-to- packaged-application • https: / / slack-chats.kotlinlang.org/t/16371916/hey-there-wave-i-m-trying-to-publish-my-app-on-the-mac-app-s#0be57312-7e43-4d30- add9-1cd5a0b7421b • https: / / firebase.google.com/products/crashlytics • https: / / docs.sentry.io/platforms/java/ • https: / / github.com/touchlab/CrashKiOS • https: / / github.com/touchlab/Kermit • https: / / github.com/adrielcafe/lyricist • https: / / www.jetbrains.com/help/kotlin-multiplatform-dev/compose-images-resources.html#strings • https: / / www.marcogomiero.com/posts/2024/kmp-ci-android • https: / / www.marcogomiero.com/posts/2024/kmp-ci-ios • https: / / www.marcogomiero.com/posts/2024/kmp-ci-macos-github-releases • https: / / www.marcogomiero.com/posts/2024/kmp-ci-macos-appstore • https: / / www.marcogomiero.com/posts/2024/compose-macos-app-store/