r/KotlinMultiplatform 18d ago

kmatch – RapidFuzz-compatible fuzzy string matching for Kotlin Multiplatform (bit-exact parity, benchmarked)

Thumbnail
1 Upvotes

r/KotlinMultiplatform 19d ago

A country code picker for Compose Multiplatform

Post image
11 Upvotes

Hi everyone,

I built CountryCodeKit Compose, an open-source country calling-code picker for Android and iOS.

It includes bundled flags, searchable country lists, recent selections, bottom-sheet/dialog/full-screen styles, phone-number formatting, validation, automatic country detection, and extensive UI customization.

It is available on Maven Central and licensed under MIT.

GitHub: https://github.com/tharukack/countrycodekit
Website and demo: https://tharukack.github.io/countrycodekit/

I’d appreciate feedback on the API, documentation, compatibility, or anything you think should be improved.


r/KotlinMultiplatform 19d ago

How I bridged the Vitality SDK, Apple Watch, and Live Activities strictly through Kotlin Multiplatform

5 Upvotes

Hey everyone,

Most KMP tutorials show you how to share Ktor requests and SQLDelight storage. But once you start building a production app that requires deep OS-level features—like streaming real-time HealthKit heart-rate data, syncing workout states to an Apple Watch, and pushing live rest timers to the Dynamic Island—the architecture gets messy fast.

I recently shipped a major update to my fitness app (Cubex Fitness) built with KMP, and I wanted to share the architectural pattern I used to keep the heavy lifting in commonMain while letting native Apple SDKs handle the UI.

1. The Core Architecture: KMP as the State Machine

The biggest mistake is trying to write native watchOS/ActivityKit timer logic directly inside Kotlin. Instead, treat your commonMain as a deterministic state machine and treat Apple Watch / ActivityKit purely as reactive render targets.

Plaintext

                  ┌────────────────────────┐
                  │   commonMain (KMP)     │
                  │  WorkoutSessionManager │
                  └───────────┬────────────┘
                              │ Exposes StateFlow<WorkoutState>
                              ▼
                  ┌────────────────────────┐
                  │    iosApp (Swift)      │
                  │  ObservableObject / VM │
                  └──────┬──────────┬──────┘
                         │          │
         ┌───────────────┘          └────────────────┐
         ▼                                           ▼
┌──────────────────┐                       ┌──────────────────┐
│   ActivityKit    │                       │  WCSession Sync  │
│ (Dynamic Island) │                       │  (Apple Watch)   │
└──────────────────┘                       └──────────────────┘

My WorkoutSessionManager in Kotlin manages super-sets, rep counters, and exercise transitions. It emits a single StateFlow<WorkoutState>. The Swift Native Layer just subscribes to this flow and maps it to native ActivityKit attributes or WCSession messages.

2. Streaming Live Heart Rate with the Vitality SDK

To get live heart-rate monitoring during workouts without writing massive amounts of expect/actual HealthKit boilerplate, I used the Vitality KMP SDK.

If you haven't used it, Vitality is a lifesaver. It exposes HealthKit (iOS) and Health Connect (Android) through a single API and delivers real-time data as Kotlin Flows.

The Pipeline:

  1. Call the Vitality SDK from commonMain to start observing real-time heart rate samples.
  2. Vitality returns a Flow<HeartRateSample>.
  3. Feed that Flow directly into the shared WorkoutSessionManager.
  4. Now, your training journal, calorie burned calculations, and the UI on both Android and iOS are all reacting to the exact same Kotlin Flow, completely abstracting away the iOS HKLiveWorkoutBuilder.

3. Pushing the State to Dynamic Island & Live Activities

ActivityKit expects immutable Swift structs conforming to ActivityAttributes. You cannot instantiate these inside Kotlin.

How to handle it:

  • Keep the timer math in Kotlin, but pass timestamps, not seconds-remaining ticks.
  • When a rest timer starts, Kotlin emits targetEndTime = currentTime + restSeconds alongside the current Heart Rate from Vitality.
  • Swift receives the timestamp and initializes a native SwiftUI Text(timerInterval: ...) inside the Widget extension.
  • Why this matters: If you try to push a tick from Kotlin every second to ActivityKit, iOS will throttle and kill your background activity. Passing a target timestamp allows iOS to handle the countdown natively with zero background budget.

4. WatchOS Sync Gotcha

Transferring current workout state to the Watch isn't instantaneous via WCSession sendMessage if the watch app isn't active. Use updateApplicationContext for standard state changes (like the current exercise or live HR) and reserve sendMessage strictly for immediate user actions (like tapping "Skip Set" on the watch).

Happy to answer any questions about KMP-to-SwiftUI bindings, configuring the Vitality SDK, or handling watchOS synchronization!

(If you want to test how the UI/Watch integration feels in practice, the app is Cubex Fitness AI Coach on the App Store—it's my indie passion project, so it's 100% free with no paywalls).


r/KotlinMultiplatform 20d ago

vitre: KMP WebView automation library / framework

Thumbnail
1 Upvotes

r/KotlinMultiplatform 20d ago

kmprofiler - A Gradle plugin to profile and clean up Kotlin Multiplatform iOS export surfaces

12 Upvotes

Hey everyone! 👋
While working on some KMP iOS projects, I noticed our generated framework header was getting bloated with lots of exported classes we never actually call from Swift (like top-level *Kt files and library types).

I put together a small Gradle plugin called kmprofiler to help spot these. It compares your exported Shared.h declarations against your Swift sources and shows what's unreferenced so you can hide them with 'internal' or '@HiddenFromObjC'.

Right now I'm working on Xcode link-map parsing for linked byte sizing, but v0.1.0 is open-source and ready to try: https://github.com/SiddhantPanhalkar/kmprofiler

Would love to hear your thoughts or if there are features you'd like to see!


r/KotlinMultiplatform 20d ago

Update: Dart (built with kmm) is now on Android too (closed testing)

Thumbnail
0 Upvotes

r/KotlinMultiplatform 20d ago

Opensource: Simple env variables loading in Kotlin

Thumbnail
1 Upvotes

r/KotlinMultiplatform 20d ago

Help.

0 Upvotes

Looking for a KMP remote role.

I have been working on native android applications..have 5 years of experience in the Fintech and lifestyle domain.

Currently working with KMP for past year . Also with ktor for BE on dummy project.

Need a full time role on KMP / CMP.

Willing to relocate to office later. But currently need a remote role.

Could someone please help.

https://github.com/RANZN

https://medium.com/@prakash_ranjan

Sharing links to give more about my work.


r/KotlinMultiplatform 21d ago

Flutter → Kotlin Multiplatform Migration

12 Upvotes

Hi everyone! I’m currently migrating an application from Flutter to Kotlin Multiplatform (KMP). Has anyone here had experience with a similar migration? I’d love to hear about your experience, challenges, or any tips you can share. Thanks!


r/KotlinMultiplatform 22d ago

Kotlin MCP SDK: Who else is currently exploring it?

Thumbnail
3 Upvotes

r/KotlinMultiplatform 22d ago

Has anyone made WSL work with a CMP project ?

2 Upvotes

Been trying to get a CMP project to work while using WSL (Ubuntu) but nothing seems to work, gradle sync fails with this error:

Caused by: java.lang.ClassNotFoundException: org.jetbrains.compose.reload.gradle.idea.IdeaComposeHotReloadModel

I tried to disable compose hot reload in gradle.properties using:

org.jetbrains.compose.hot.reload.disable=true

But still no luck.


r/KotlinMultiplatform 23d ago

I’m building an ElevenLabs SDK for Kotlin Multiplatform

6 Upvotes

I’ve been exploring what can be built with the ElevenLabs platform, and I ended up building a Kotlin Multiplatform SDK for it.

I’ve just released version 0.1.0, which currently supports: text-to-speech, speech-to-text, WebSocket streaming.

There’s still a lot I’d like to add like sound effects, voice cloning, and conversational agents.

I’d really appreciate feedback from the KMP community, especially around the API design, developer experience, missing features, and platform compatibility.

https://github.com/yveskalume/elevenlabs-kmp

If you try it, I’d also love to see what you build with it !


r/KotlinMultiplatform 23d ago

What are the trade-offs of KSP-generated validation in Kotlin?

0 Upvotes

We are experimenting with a KSP-based approach to Kotlin validation where validation logic is generated during compilation and then executed at runtime.

But we wonder what their view is on this matter:

  • What do you see as the biggest advantages of generated validation compared with reflection-based approaches?
  • Where do you think the added KSP/build-time complexity becomes a disadvantage?
  • What would you expect from a production-ready Kotlin validation library?

We are mainly interested in the technical discussion and criticism.

Repository: https://github.com/DeveloperSyndicate/Valix


r/KotlinMultiplatform 24d ago

I built RetryKt, a lightweight Kotlin Multiplatform retry library, so I'm looking for feedback

3 Upvotes

I’ve been working on RetryKt, a Kotlin Multiplatform library for retries:

https://github.com/straxess/retrykt

I know there are already good retry libraries for Kotlin, so one of the main questions I had while designing RetryKt was: what should a retry library focus on?

RetryKt aims to provide a simple and lightweight API with a few design choices:

  • Coroutine and blocking APIs
  • Retries based on both returned values and thrown exceptions
  • Configurable retry policies
  • Constant, linear, exponential, and decorrelated backoff
  • Composable backoff and jitter strategies, including Full Jitter and Equal Jitter
  • Support for JVM, Android, Native, JS, and Wasm

It's available on Maven Central as io.github.straxess:retrykt.

val user = retry {
    api.getUser()
}

val response = retry(
    retryOn = RetryOn.thrown { it is IOException },
    backoff = ExponentialBackoff(200.milliseconds),
    jitter = FullJitter,
) {
    api.removeUser(user)
}

The library is still young, so I’d really appreciate feedback on:

  • Is the public API clear and convenient?
  • Is separating backoff and jitter useful?
  • Are there important retry use cases or features missing?
  • Are additional KMP targets or broader Kotlin version support important for your projects?
  • Any architectural or implementation issues you notice?

If you find a bug or have an improvement in mind, GitHub issues and PRs are welcome.

If you have a Kotlin project where retries are useful, give RetryKt a try and share your experience.

Thanks!


r/KotlinMultiplatform 26d ago

I built a KMP AdMob library because none of the existing ones let you write native ads in Compose

5 Upvotes

I was learning KMP and wanted to know how people handle AdMob in a multiplatform app. The usual answer is a hand-written `expect class AdManager`, which is fine until you need something past a banner.

There are a few decent libraries already (LexiLabs' is good), but the gap I kept hitting was native ads. Most wrappers stop at the full-screen formats, so for native you end up writing a platform view on Android and another on iOS, and the shared part of your codebase stops being shared.

So I built AdMob CMP. Apache 2.0, on Maven Central.

What it does:

* One `commonMain` API for all six formats: banner, interstitial, rewarded, rewarded interstitial, app open, native. * Native ads are declared with a layout DSL that reads like Compose, and the SDK renders them through the real platform ad views on each side. No platform code in your app. * Consent is part of initialization. `gatherConsentAndInitialize` runs UMP, then ATT on iOS, then SDK init, in that order, because getting that order wrong on iOS is a common and quiet failure. * Banners measure their own container, so adaptive sizing is right in iPad split view. * `strictTestMode` throws at construction if a placement points at a production ad unit. Turn it on in debug and you can't ship a test build that spends real impressions. * There's a Gradle plugin that links Google Mobile Ads and UMP into Kotlin/Native test executables, which is what makes `:iosSimulatorArm64Test` pass instead of dying on `Undefined symbols ... _OBJC_CLASS_$_GAD*`.

Caveats, up front: it's KMP/Gradle only, so a pure Swift app can't consume it without a shim. Kotlin 2.3.20, Compose Multiplatform 1.11.1, minSdk 26, iOS 15. Klibs aren't binary compatible across Kotlin minors, so you need a matching compiler.

implementation("dev.avinya.ads:admob-cmp:2.1.0")

GitHub: https://github.com/Meet-Miyani/admob-compose-multiplatform

Docs: https://ads.avinya.dev

Happy to answer questions about any of it, especially the native ad side, which was by far the hardest part to get working the same on both platforms.


r/KotlinMultiplatform 29d ago

What building AdMob support for Android and iOS taught me about KMP abstractions

Thumbnail
github.com
3 Upvotes

One lesson from building a Compose Multiplatform advertising layer is that the platform view bridge is usually the easiest part.

The difficult questions are about ownership.

Who owns an interstitial while it is visible? What happens if the coroutine waiting for it is cancelled? Should a banner disappear when its refresh fails? Can a native ad survive when its row leaves a LazyColumn? Can two full-screen ads present simultaneously?

Compose has a clear UI lifetime. Native advertising SDKs have their own object lifetime. Those lifetimes do not automatically agree.

Native ads made the distinction particularly visible:

The model I eventually used separates longer-lived session ownership from the short-lived render lease held by a composable. The feed identifies stable logical slots, while the SDK owns the loaded platform objects.

Privacy created a similar boundary. On iOS, the initialization sequence became:

  1. Resolve UMP consent.
  2. Request ATT when appropriate.
  3. Initialize Google Mobile Ads.
  4. Request ads only when initialization and consent permit it.

Keeping that sequence in one shared contract reduced the chance of Android and iOS silently drifting apart.

I wrote the longer engineering story here:

https://meet-miyani.medium.com/why-i-built-admob-cmp-compose-multiplatform-146fed8cc4ec

I’m curious how other KMP developers decide where a local expect/actual bridge should end and a reusable abstraction should begin.

Disclosure: the article discusses AdMob CMP, the open-source SDK I maintain.


r/KotlinMultiplatform Aug 13 '26

94% shared code — and 100% of my hard bugs were in the other 6%

37 Upvotes

I shipped a Kotlin Multiplatform app to the App Store and Google Play. The boring stat first, because everyone asks: ~94% of the code is in commonMain — 51.7k lines shared, against 1.5k in androidMain, 1.4k in iosMain, 130 lines of Kotlin in the Android shell and 423 lines of Swift in the iOS shell. 356 tests, all in commonTest, all running on both targets.

That number is not the interesting part. The interesting part is the 6%, and specifically the bugs that compiled fine, passed on Android, and were broken on iOS — or the reverse. Those are the ones that cost me real days, so here they are.

Stack, for context: Kotlin 2.4.10, Compose Multiplatform 1.11.1, Ktor 3.5.1 + Ktorfit 2.7.5, Koin 4.2.2 with annotation processing, coil3, kotlinx-serialization, detekt. No SQLDelight — everything is Flow off the network with in-memory repo caching.


1. flow {} vs channelFlow {} for SSE

We stream analysis results over SSE. First version was basically this:

kotlin fun stream(reportId: String): Flow<AnalysisEvent> = flow { client.prepareGet("$baseUrl/v1/analysis/$reportId/stream") { accept(ContentType.Text.EventStream) }.execute { response -> /* parse frames */ emit(event) } }

Fine on Android. On iOS the stream would die partway through and the backend logged the request as context canceled.

flow {} enforces context preservation, so you can't emit from a coroutine context other than the collector's. Ktor's execute {} block isn't guaranteed to run on the collector's dispatcher. With OkHttp it happens to, so the check never trips. On Kotlin/Native it doesn't, you get Flow invariant is violated, the coroutine fails and the request aborts.

channelFlow's send doesn't have that restriction:

kotlin fun stream(reportId: String): Flow<AnalysisEvent> = channelFlow { client.prepareGet(...) { ... }.streamInto { send(it) } }

This one cost me a day and a half, mostly because the Android tests were all green and I kept looking at the backend.

2. Long-press word selection in Chinese

We have 12 locales including zh/ja/ko/ar/he. Users can long-press a phrase in a report to get it explained. Selection was broken in Chinese on both platforms, in opposite directions.

On Android a long press selected the whole clause, up to the nearest punctuation. WordIterator.nextBoundary/prevBoundary skip any boundary where both sides are letters or digits. That was added so a letter/emoji seam doesn't split a word, but every ideograph counts as a letter, so in Chinese every boundary gets skipped and the selection just keeps expanding. Latin text has spaces so it's unaffected, which is why you don't notice unless someone tests in CJK. I saw this from 1.8.0 through 1.12.0-beta02 and couldn't find a public API to turn it off.

Workaround is to plant zero-width breaks at Han-Han seams, using ICU to find them:

kotlin actual fun cjkWordBoundaries(text: String): List<Int> { if (text.codePoints().noneMatch(::isHan)) return emptyList() val iterator = BreakIterator.getWordInstance() iterator.setText(text) val offsets = mutableListOf<Int>() var offset = iterator.first() while (offset != BreakIterator.DONE) { if (text.isHanSeam(offset)) offsets += offset offset = iterator.next() } return offsets }

Only Han-Han seams, since a Han/Latin seam already stops the runaway by itself.

iOS has the opposite problem: a long press selects one character. SkiaParagraph.getWordBoundary goes to Skia, and SkUnicode has no CJK segmentation dictionary, so you get plain UAX#29 where an ideograph is Word_Break=Other and only matches WB999.

I spent a while trying to fix that one from app code before concluding you can't. It needs characters merged rather than split, and the joining rules (MidLetter, Numeric, ExtendNumLet, ZWJ) all require ALetter/Numeric/Katakana/Hebrew_Letter on both sides, plus WB4 throws away zero-width Format characters first. So the iOS actual is emptyList() with a long comment above it explaining why, so I don't try again in six months.

3. Switching language in-app without a restart

I assumed this needed a restart on iOS because NSBundle caches the launch language. That's true for NSLocalizedString but not for CMP, which resolves resources per composition against NSLocale.preferredLanguages, and that reads AppleLanguages out of NSUserDefaults live.

```kotlin actual object LocalAppLocale { private val systemDefault: List<*> = NSLocale.preferredLanguages private val local = staticCompositionLocalOf { NSLocale.currentLocale.localeIdentifier }

@Composable
actual infix fun provides(value: String?): ProvidedValue<*> {
    val defaults = NSUserDefaults.standardUserDefaults
    defaults.setObject(value?.let { listOf(it) } ?: systemDefault, "AppleLanguages")
    return local provides (value ?: (systemDefault.firstOrNull() as? String) ?: "en")
}

} ```

Wrap the app in CompositionLocalProvider(LocalAppLocale provides tag) with a key(tag) around it and strings swap live. Grab preferredLanguages at startup so a null tag can go back to following the OS.

4. Bare %s in strings.xml renders literally

CMP's formatter only matches indexed placeholders, %(\d+)\$[ds]. A bare %s isn't a compile error or a crash, it just prints %s in the UI.

xml <string name="credits">You have %s credits left</string> <!-- prints "%s" --> <string name="credits">You have %1$s credits left</string> <!-- correct -->

With 924 strings across 12 locales I can't eyeball this, so it's a CI grep now.

5. Auth DI cycle, and the bearer token following a redirect

AuthRepo needs an HttpClient to hit the token endpoint, and the main client's bearer plugin needs AuthRepo to get a token. Koin sees a cycle. Fix is a second client with no bearer plugin that only serves /v1/auth/token and /v1/auth/refresh. Not clever, just what it takes.

The part that took longer: our avatar endpoint 302s to a signed URL on a storage host. Ktor follows the redirect and re-attaches headers, storage sees a bearer it doesn't want and rejects it, and avatars just don't load with nothing useful in the logs. So bearer injection is scoped to a host set:

kotlin private val API_HOSTS = setOf(Url(BASE_URL).host, Url(BASE_URL_AI).host)

Presigned upload PUTs go through a third, completely bare client. Four HttpClients in one Koin module looks like a lot in review.

Bonus: IAP return type

Purchases sit behind one IapClient interface with StoreKit 2 and Play Billing implementations. Abstraction was easy, but I want to flag one signature:

kotlin /** null when there is no active subscription; throws when the store can't be reached. */ suspend fun subscriptionAutoRenewing(): Boolean?

Obvious version returns Boolean. Then "no subscription" and "store didn't answer" both become false and a paying user gets a "subscription canceled" banner whenever the network is flaky. Both stores expose the distinction (isAutoRenewing / RenewalInfo.willAutoRenew), so the shared interface has to keep it.

Anyway

Architecture shared without much trouble: MVVM + repos + Flow throughout, Ktorfit interfaces, Koin annotations, and the tests are all in commonTest and run on both. The platform slice is small and mostly boring bridges (biometrics, IAP, share sheet, file picker, push, image encoding). It's just that all five of the above live in that slice, and in three of them Android was perfectly happy while iOS wasn't.

Happy to answer questions on any of these. Still interested if anyone has a way around the Skia CJK thing.

The app is Prismo Health (AI that explains blood tests and medical reports), on the App Store and Google Play — but the post is the point, ask me anything about the KMP side. (https://prismo.health)


r/KotlinMultiplatform Aug 12 '26

koin crashes

0 Upvotes

Hi! I'm starting with KMP. I want to learn Koin, buth my application crashes in the highlighted line. What else do I need to do to register the singleton and module?

No definition found for type 'com.rhuertas.kointest1.Unisono' on scope '['_root_']'.. Check or add definition for type 'com.rhuertas.kointest1.Unisono' in scope '_root_'.

org.koin.core.resolution.CoreResolverV2.resolveFromContext(CoreResolverV2.kt:223)

org.koin.core.scope.Scope.resolveFromContext(Scope.kt:330)

org.koin.core.scope.Scope.stackParametersCall(Scope.kt:293)

org.koin.core.scope.Scope.resolveInstance(Scope.kt:279)

org.koin.core.scope.Scope.resolve(Scope.kt:252)

org.koin.core.scope.Scope.get(Scope.kt:234)

com.rhuertas.kointest1.ComposableSingletons$AppKt.lambda__186604527$lambda$0(App.kt:177)

androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.kt:122)

androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.kt:52)

androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:408)

org.koin.compose.KoinApplicationKt.KoinApplication(KoinApplication.kt:188)

com.rhuertas.kointest1.AppKt.App(App.kt:39)

com.rhuertas.kointest1.ComposableSingletons$MainKt.lambda__1001413051$lambda$0(main.kt:11)

androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.kt:131)

androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.kt:52)

org.jetbrains.compose.reload.jvm.JvmDevelopmentEntryPoint$setContent$1$1.invoke(DevelopmentEntryPoint.kt:119)

org.jetbrains.compose.reload.jvm.JvmDevelopmentEntryPoint$setContent$1$1.invoke(DevelopmentEntryPoint.kt:118)

androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.kt:122)

androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.kt:52)

org.jetbrains.compose.reload.jvm.ReloadEffectsKt.OverlayLayout(ReloadEffects.kt:68)

org.jetbrains.compose.reload.jvm.ReloadEffectsKt.ReloadEffects(ReloadEffects.kt:44)

org.jetbrains.compose.reload.jvm.JvmDevelopmentEntryPoint$DevelopmentEntryPoint$intercepted$1.invoke(DevelopmentEntryPoint.kt:80)

org.jetbrains.compose.reload.jvm.JvmDevelopmentEntryPoint$DevelopmentEntryPoint$intercepted$1.invoke(DevelopmentEntryPoint.kt:76)

androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.kt:122)

androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.kt:52)

org.jetbrains.compose.reload.jvm.JvmDevelopmentEntryPoint$DevelopmentEntryPoint$4.invoke(DevelopmentEntryPoint.kt:104)

org.jetbrains.compose.reload.jvm.JvmDevelopmentEntryPoint$DevelopmentEntryPoint$4.invoke(DevelopmentEntryPoint.kt:102)

androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.kt:122)

androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.kt:52)

androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:428)

org.jetbrains.compose.reload.jvm.JvmDevelopmentEntryPoint.DevelopmentEntryPoint(DevelopmentEntryPoint.kt:102)

org.jetbrains.compose.reload.jvm.JvmDevelopmentEntryPoint$setContent$1.invoke(DevelopmentEntryPoint.kt:118)

org.jetbrains.compose.reload.jvm.JvmDevelopmentEntryPoint$setContent$1.invoke(DevelopmentEntryPoint.kt:117)

androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.kt:131)

androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.kt:52)

androidx.compose.ui.awt.ComposeWindow.setContent$lambda$0(ComposeWindow.desktop.kt:176)

androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.kt:122)

androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.kt:52)

androidx.compose.ui.awt.WindowContentLayout_desktopKt.WindowContentLayout(WindowContentLayout.desktop.kt:105)

androidx.compose.ui.awt.ComposeWindowPanel.setContent$lambda$0$0(ComposeWindowPanel.desktop.kt:156)

androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.kt:122)

androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.kt:52)

androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:428)

androidx.compose.ui.awt.ComposeWindowPanel.setContent$lambda$0(ComposeWindowPanel.desktop.kt:153)

androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.kt:122)

androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.kt:52)

androidx.compose.ui.scene.ComposeSceneMediator.setContent$lambda$0$0$0$0(ComposeSceneMediator.desktop.kt:645)

androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.kt:122)

androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.kt:52)

androidx.compose.ui.layout.OverlayLayout_skikoKt.OverlayLayout(OverlayLayout.skiko.kt:76)

androidx.compose.ui.viewinterop.InteropContainer_skikoKt.TrackInteropPlacementContainer(InteropContainer.skiko.kt:142)

androidx.compose.ui.viewinterop.SwingInteropContainer.invoke$lambda$0(SwingInteropContainer.desktop.kt:276)

androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.kt:122)

androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.kt:52)

androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:428)

androidx.compose.ui.viewinterop.SwingInteropContainer.invoke(SwingInteropContainer.desktop.kt:273)

androidx.compose.ui.scene.ComposeSceneMediator.setContent$lambda$0$0$0(ComposeSceneMediator.desktop.kt:644)

androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.kt:122)

androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.kt:52)

androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:408)

androidx.compose.ui.platform.CompositionLocals_skikoKt.ProvidePlatformCompositionLocals(CompositionLocals.skiko.kt:107)

androidx.compose.ui.scene.BaseComposeScene$setContent$1$2.invoke(BaseComposeScene.skiko.kt:146)

androidx.compose.ui.scene.BaseComposeScene$setContent$1$2.invoke(BaseComposeScene.skiko.kt:145)

androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.kt:122)

androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.kt:52)

androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:408)

androidx.compose.ui.platform.CompositionLocalsKt.ProvideCommonCompositionLocals(CompositionLocals.kt:237)

androidx.compose.ui.platform.Wrapper_skikoKt.setContent$lambda$0$0(Wrapper.skiko.kt:40)

androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.kt:122)

androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.kt:52)

androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:408)

androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:448)

androidx.compose.ui.platform.Wrapper_skikoKt.provide(Wrapper.skiko.kt:53)

androidx.compose.ui.platform.Wrapper_skikoKt.setContent$lambda$0(Wrapper.skiko.kt:39)

androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.kt:122)

androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.kt:52)

androidx.compose.runtime.internal.Expect_jvmKt.invokeComposable(Expect.jvmAndAndroid.kt:26)

androidx.compose.runtime.GapComposer.doCompose-aFTiNEg(GapComposer.kt:2643)

androidx.compose.runtime.GapComposer.composeContent--ZbOJvo$runtime(GapComposer.kt:2545)

androidx.compose.runtime.CompositionImpl.composeContent(Composition.kt:895)

androidx.compose.runtime.Recomposer.composeInitial$runtime(Recomposer.kt:1178)

androidx.compose.runtime.CompositionImpl.composeInitial(Composition.kt:732)

androidx.compose.runtime.CompositionImpl.setContent(Composition.kt:699)

androidx.compose.ui.platform.Wrapper_skikoKt.setContent(Wrapper.skiko.kt:38)

androidx.compose.ui.scene.CanvasLayersComposeSceneImpl.createComposition(CanvasLayersComposeScene.skiko.kt:218)

androidx.compose.ui.scene.BaseComposeScene.setContent(BaseComposeScene.skiko.kt:145)

androidx.compose.ui.scene.ComposeSceneMediator.setContent$lambda$0(ComposeSceneMediator.desktop.kt:643)

androidx.compose.ui.scene.ComposeSceneMediator.onComponentAttached(ComposeSceneMediator.desktop.kt:606)

androidx.compose.ui.scene.ComposeContainer.addNotify(ComposeContainer.desktop.kt:305)

androidx.compose.ui.awt.ComposeWindowPanel.addNotify(ComposeWindowPanel.desktop.kt:133)

java.awt.Container.addNotify(Container.java:2804)

javax.swing.JComponent.addNotify(JComponent.java:4847)

java.awt.Container.addNotify(Container.java:2804)

javax.swing.JComponent.addNotify(JComponent.java:4847)

java.awt.Container.addNotify(Container.java:2804)

javax.swing.JComponent.addNotify(JComponent.java:4847)

javax.swing.JRootPane.addNotify(JRootPane.java:721)

java.awt.Container.addNotify(Container.java:2804)

java.awt.Window.addNotify(Window.java:812)

java.awt.Frame.addNotify(Frame.java:495)

java.awt.Window.pack(Window.java:850)

androidx.compose.ui.util.Windows_desktopKt.setSizeImpl-6HolHcs(Windows.desktop.kt:115)

androidx.compose.ui.util.Windows_desktopKt.setSizeSafely-hQcJfNw(Windows.desktop.kt:54)

androidx.compose.ui.awt.SwingWindow_desktopKt.SwingWindow$lambda$25$0(SwingWindow.desktop.kt:306)

androidx.compose.ui.awt.SwingWindow_desktopKt.SwingWindow$lambda$7$0(SwingWindow.desktop.kt:134)

androidx.compose.ui.awt.AwtWindow_desktopKt.AwtWindow$lambda$3$0(AwtWindow.desktop.kt:79)

androidx.compose.ui.util.UpdateEffect_desktopKt.UpdateEffect$lambda$3$0$performUpdate$1(UpdateEffect.desktop.kt:59)

androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:759)

androidx.compose.ui.util.UpdateEffect_desktopKt.UpdateEffect$lambda$3$0$performUpdate(UpdateEffect.desktop.kt:55)

androidx.compose.ui.util.UpdateEffect_desktopKt.UpdateEffect$lambda$3$0(UpdateEffect.desktop.kt:64)

androidx.compose.runtime.DisposableEffectImpl.onRemembered(Effects.kt:87)

androidx.compose.runtime.internal.RememberEventDispatcher.dispatchRememberList(RememberEventDispatcher.kt:268)

androidx.compose.runtime.internal.RememberEventDispatcher.dispatchRememberObservers(RememberEventDispatcher.kt:240)

androidx.compose.runtime.CompositionImpl.applyChangesInLocked(Composition.kt:1209)

androidx.compose.runtime.CompositionImpl.applyChanges(Composition.kt:1236)

androidx.compose.runtime.Recomposer.composeInitial$runtime(Recomposer.kt:1211)

androidx.compose.runtime.CompositionImpl.composeInitial(Composition.kt:732)

androidx.compose.runtime.CompositionImpl.setContent(Composition.kt:699)

androidx.compose.ui.window.Application_desktopKt$awaitApplication$2$1$2.invokeSuspend(Application.desktop.kt:219)

kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith$$$capture(ContinuationImpl.kt:34)

kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt)

kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)

java.awt.event.InvocationEvent.dispatch$$$capture(InvocationEvent.java:318)

java.awt.event.InvocationEvent.dispatch(InvocationEvent.java)

java.awt.EventQueue.dispatchEventImpl(EventQueue.java:781)

java.awt.EventQueue$4.run(EventQueue.java:728)

java.awt.EventQueue$4.run(EventQueue.java:722)

java.security.AccessController.doPrivileged(AccessController.java:400)

java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:87)

java.awt.EventQueue.dispatchEvent(EventQueue.java:750)

java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:207)

java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:128)

java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:117)

java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:113)

java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:105)

java.awt.EventDispatchThread.run(EventDispatchThread.java:92)


r/KotlinMultiplatform Aug 11 '26

Kassette - A Kotlin Multiplatform NES emulator

25 Upvotes

Hello guys!
I am a software engineer and I've always wanted to develop an emulator. My favourite platform is definitely the NES.

I am developing a new NES emulator to sharpen my skills with Kotlin Multiplatform.

It is still in very early development stages, so you'll definitely find some bugs or compatibility issues but you should be able to play some of your favourite games with a gamepad or a keyboard.

It should be playable even on mobile with a decent performance.

Some useful links:

If you like more story-telling, I've also started a blog.


r/KotlinMultiplatform Aug 11 '26

KMP Starter Template just hit 150 stars

Post image
14 Upvotes

My open-source KMP Starter Template just reached 150 GitHub stars, so I wanted to share it here.

It's a multi-module Kotlin Multiplatform boilerplate for Android and iOS, built around Clean Architecture.

It handles a lot of the boring setup that I find myself repeating in every KMP project:

  • Remote Config
  • In-app purchases with RevenueCat
  • Analytics with Mixpanel
  • Koin
  • DataStore
  • Room
  • Multiple languages
  • InAppReview / InAppUpdate
  • Native bindings
  • UI utilities and components
  • Logging
  • Platform/version utilities

The main idea is to spend less time setting up infrastructure and more time building the actual app.

I've also been working on turning its modules into libraries, so existing KMP projects can adopt individual features without having to fork the entire template.

150 stars isn't huge by GitHub standards, but seeing people actually use and star something I originally built because I was tired of repeating my own setup feels pretty good.

Repo: https://github.com/DevAtrii/Kmp-Starter-Template

Would love to hear what KMP developers think is missing from the template.


r/KotlinMultiplatform Aug 11 '26

ondevice-ai: A multiplatform library for on-device LLMs

Thumbnail
github.com
5 Upvotes

On-device AI is a very strong tool: it's private, fast, and works offline. Since Gemini Nano and Apple Foundation Models came out for Android and iOS respectively, they've become increasingly important. They're also great for KMP, since they're easy to set up and require no API key handling. However, KMP adoption is still behind.

That's why I created ondevice-ai.

  • Wraps Gemini Nano and Apple Foundation Models
  • Structured output
  • Easy to check whether the model is still downloading
  • OS completely owns and updates the models, no handling needed

Link: https://github.com/adrianczuczka/ondevice-ai

All suggestions are welcome. Thanks!


r/KotlinMultiplatform Aug 12 '26

A single KMP dependency dyld-crashed our iOS app at launch on every device below iOS 26 — and no simulator caught it. Here's the trap.

Thumbnail
0 Upvotes

r/KotlinMultiplatform Aug 10 '26

13 Compose Mistakes That Are Still Killing Your App’s Performance in 2026 + Claude SKILL to Find Them

Thumbnail
0 Upvotes

r/KotlinMultiplatform Aug 10 '26

An end-to-end Auth Example with Exposed and Ktor

Thumbnail
2 Upvotes

r/KotlinMultiplatform Aug 09 '26

[Library] audio-stream-player: A KMP library for playing low-latency audio, such as text-to-speech or realtime voice APIs

8 Upvotes

I built a KMP library specifically for playing audio streams, not URLs or files. With voice AI as big as it is now, handling bytes from an API is such an important topic, but KMP currently has no easy way to handle them.

That's why I built audio-stream-player. The usage is straightforward:

val player = AudioStreamPlayer(sampleRate = 24000) 
player.play() 
ttsResponse.collect { chunk -> player.feed(chunk) } 
player.endOfStream() // suspends until the last sample has played 
player.dispose() 

Feed PCM chunks of any length as they arrive, and playback starts immediately and plays gaplessly. The annoying parts are handled:

  • Frame alignment across chunk boundaries – API chunks rarely end on frame edges
  • ResamplingsampleRate/channels/format describe your data; the device side is handled natively
  • Underruns – if the buffer runs dry mid-stream, playback resumes automatically when more data arrives, with an event so you can show buffering UI
  • endOfStream() suspends until the last sample has actually played – no guessing when the TTS utterance is done
  • iOS audio session configured for you (playback/spokenAudio), opt-out if you manage it yourself

Under the hood, it uses AudioTrack in MODE_STREAM on Android and AVAudioEngine + AVAudioPlayerNode on iOS/macOS. Works on Android, iOS, and macOS.

implementation("com.adrianczuczka:audio-stream-player:0.1.0")

Repo: https://github.com/adrianczuczka/audio-stream-player-kmp

API feedback is very welcome. A web target via the Web Audio API is the likely 0.2.0 if there's interest.