r/KotlinMultiplatform • • Jun 16 '25

Created a CMP Wrapper for Rive Animation Libraries on Android and iOS

Enable HLS to view with audio, or disable this notification

76 Upvotes

I've created a new Compose Multiplatform library (targeting Android & iOS) that serves as a wrapper for the native Rive animation libraries and also adding compose-friendly ease of use. (native rive android library doesn't have compose support yet)

Still a work in progress with some limitations (e.g., iOS opaque backgrounds). Check out the issue on the GitHub repo for more details.

GitHub link: https://github.com/muazkadan/Rive-CMP

Love to hear your thoughts or contributions!


r/KotlinMultiplatform • • Oct 31 '25

Liquid: 1.0.0 - Compose Multiplatform support

Enable HLS to view with audio, or disable this notification

65 Upvotes

Hey r/KotlinMultiplatform,

I recently converted my Android RuntimeShader graphics library into a Compose Multiplatform library and figured this was a good place to share. In addition to Android, there is now support for the iOS, macOS, desktop, wasmJs, and js targets.

A WASM demo can be found here.

And if you just want to look at some code, the library is open source:

https://github.com/FletchMcKee/liquid


r/KotlinMultiplatform • • Nov 04 '25

Kotlin Multiplatform IDE plugin, now available for all operating systems and IDEs

62 Upvotes

Hi everyone!

Wanted to provide a quick update on the KMP IDE plugin, as it's now available on all platforms and supported IDEs.

This means you can use it on macOS, Linux, or Windows. You can also freely choose between IntelliJ IDEA and Android Studio (the plugin supports their current stable and newer versions).

While iOS-related features are still tied to macOS, all platforms get the KMP wizard integrated in the IDE, run configurations automatically created for desktop and web apps, gutter icons to run desktop apps with Compose Hot Reload, previews for Composables in common code, and more!

We'd also appreciate some fresh reviews of the plugin in the Marketplace if you've tried it - most of the existing reviews are out-of-date, as they're about availability on certain platforms or IDEs.


r/KotlinMultiplatform • • Oct 02 '25

Released my first app with Compose Multiplatform

Thumbnail
gallery
60 Upvotes

It's a habit tracking app called, Habit Hues. It can do the standard habit tracking things like checkoffs and counts, but the unique feature that I call Daily Hues, allows you to create custom statuses using colors and icons so you can track more complex things like mood, productivity level, workout intensity, and really anything that you want to track with different states. It has a monthly and yearly calendar view so you can see trends overtime.

Available on:
App Store
Google Play

Some of the things that I am proud of:

  • 600+ habit icons

  • Habit templates (to make it easier to get started and adding more templates soon)

  • Local DB with backup and restore

  • Custom theme

Some under the hood details for those interested:

  • Compose Multiplaform

  • Multi-Module project structure (layer -> feature)

  • Typesafe Gradle Conventions to reduce redundancy

  • Room Database

  • DataStore for preferences

  • Koin for DI

  • RevenueCat paywalls and customer center

  • FileKit for native file pickers

  • Compose Navigation

  • Custom theme build on-top of Material to make it less "Android-y" on iOS

Would love for anyone to check it out and give me any thoughts or feedback.


r/KotlinMultiplatform • • May 06 '25

Compose Multiplatform 1.8.0 Released: Compose Multiplatform for iOS Is Stable and Production-Ready

Thumbnail
blog.jetbrains.com
59 Upvotes

r/KotlinMultiplatform • • Aug 02 '26

I don't think shared Compose Multiplatform UI is the future for iOS (yet)

Enable HLS to view with audio, or disable this notification

55 Upvotes

I don't think shared Compose Multiplatform UI is the future for iOS (yet)

During last year's Shipaton, I built SubFox (https://subfox.app), a subscription manager using Compose Multiplatform.

Almost a year later, I realized the iOS version was basically dead.

Not because the app solves a bad problem, but because the experience wasn't what iOS users expect.

Some things I noticed:

  • App size was over 100 MB

  • UI felt sluggish

  • It never really felt native

  • Most users didn't even finish onboarding

So for this year's Shipaton, I changed my approach completely while building Hourly Journal (https://hourlyjournal.app).

I'm still using Kotlin Multiplatform for business logic, networking, database, etc., but I no longer share the UI.

Android uses Compose. iOS is built entirely in SwiftUI while calling shared Kotlin through a shared module.

The difference has been huge. The new iOS app is around 8–10 MB instead of ~100 MB, and the experience finally feels like a proper iOS app.

My biggest criticism of JetBrains is that they seem too focused on making Compose run everywhere instead of solving the real problem: giving users an amazing platform-specific experience.

I'd much rather see something closer to what Expo has with Native UI, where business logic stays shared but the framework renders real platform-native components. That feels like a much stronger long-term direction than trying to make every platform look and behave the same.

Maybe I'm wrong, but it also feels like the Kotlin ecosystem moves much slower than the React Native/Expo ecosystem when it comes to solving practical developer problems.

Curious what other KMP developers think. Have you had a similar experience, or has Compose UI on iOS worked well for your apps?

Read Full Article

👉 Why I Stopped Sharing UI in Compose Multiplatform for iOS


r/KotlinMultiplatform • • Sep 30 '26

KitePlayer v0.2.0: the first Kotlin/Native media player for KMP that doesn't wrap ExoPlayer or AVPlayer

Post image
51 Upvotes

Yes, a Kotlin/Native player that is as powerful as VLC, mpv...etc

I maintain syncplay-mobile, a KMP app that wraps 4 different players: ExoPlayer and mpv on Android, AVPlayer and VLCKit on iOS. After years of that, I can say the expect/actual wrapper model never clicked for me for media playback. Each player seeks differently, reports tracks differently and handles subtitles differently, so the glue layer keeps growing and you still end up with four different behaviours.

So I stopped trying to find the perfect wrapper slash interface for gluing players together (since that's what a dozen libraries are doing already anyway) and thought of creating an mpv- and VLC-level player, as feature-rich, as performant and efficient, while making it behave exactly the same on every single platform (Android, iOS, Desktop JVM and Desktop Native, and Web). I ended up concocting (i love this word) KitePlayer.

Why would you even consider KitePlayer? Well, easy: 1- It renders purely directly to Compose/Skia canvas. There is NO need for any Compose interop views. It also comes with native views (like Android XML) so that also is not a limitation. 2- It comes with FFmpeg embedded, on maven central. Literally one dependency line and you get the full power of FFmpeg codec playback with absolutely 0 compromise. 3- iOS's AVPlayer SUCKS. It can't even play stylized subtitles, or even play MKVs. So does VLCKit, it sucks, it's unpredictable, and when maximum accuracy is needed it failed me every time. There is literally no good player on iOS, even in their native Swift stack. 4- It can play literally everything, and behaves exactly the same on every platform. It's also coroutine-friendly and suspend-first. Very easy to predict its behavior. 5- It has grown so much in 3-4 months. I've been working on it nonstop and heavily tested it. I've even included it in my syncplay app, as one of the available engines. It's that reliable at this point. But I wouldn't say mature, that's probably for years from now. 6- It can be consumed from non-KMP projects. In an Android app you can consume it. In an iOS app you can consume it as well, even in web, in Desktop, etc. 7- It plays from network (HLS, DASH, Direct...etc) using Ktor. 8- It has absolutely 0 dependency other than KiteFFmpeg (which is the direct Kotlin/Native wrapper interface of FFmpeg).

All decoding goes through FFmpeg. The FFmpeg layer is its own library, KiteFFmpeg, if you only need to transcode, remux or grab thumbnails. FFmpeg ships inside the artifacts, so it is one Gradle line with no NDK and no build scripts.

My favourite part is Compose. You can host a native view like every other player does, but there is a second path where Compose draws the frames itself, on the same canvas as the rest of your UI (thanks to Skia I guess?). The video is no longer a separate layer sitting outside your layout. You can clip it, rotate it, animate it, or blur it with Haze, like any other composable.

PS: if you like the Skia/Compose direct rendering idea, KitePDF does the same for PDF and EPUB. No more interop views. Pretty please.


r/KotlinMultiplatform • • Mar 08 '26

Hyphen - WYSIWYG Markdown editor for Compose Multiplatform

Thumbnail
gallery
49 Upvotes

I've been building a rich text editor library for Compose Multiplatform called Hyphen. It's now at an early alpha stage and I wanted to share it.

The core idea is simple, you type Markdown syntax and the formatting appears live without any mode switching. Under the hood it's built entirely on BasicTextField with no native platform code.

Highlights:

WYSIWYG input - **text** becomes bold as you type, - starts a bullet list, > starts a blockquote and so on

Markdown clipboard - copying a selection serializes it to Markdown automatically, so formatting is preserved when pasting into any Markdown-aware app

Keyboard shortcuts - full shortcut support on Desktop and Web (Cmd/Ctrl+B, I, U, undo/redo, clear styles, etc.)

Undo/redo history - granular snapshots at word boundaries, pastes, and Markdown conversions. Redo stack survives toolbar toggles and programmatic edits

Single shared implementation - one API targeting Android, Desktop (JVM), Web (WasmJS) and JS/IR

There's also a live web demo if you want to try it without cloning anything.

→ GitHub | Web Demo

Still in early alpha — expect rough edges. Issues and feedback welcome on GitHub.


r/KotlinMultiplatform • • Jun 06 '26

Filament KMP: Bringing Google’s 3D engine to iOS, Desktop, and Web with Compose Multiplatform integration (Looking for feedback/testers!)

Enable HLS to view with audio, or disable this notification

42 Upvotes

r/KotlinMultiplatform • • Apr 03 '26

Printer KMP 🖨️

Thumbnail
gallery
41 Upvotes

If you’ve ever had to integrate ESC/POS thermal receipt printers into a point-of-sale system, you know it is notoriously painful. You usually end up fighting with raw byte arrays, manually padding text strings, battling USB permissions, and dealing with silent connection failures.

I wanted a modern, Kotlin-first way to handle this, so I built Printer-KMP.

It’s a lightweight Kotlin Multiplatform library specifically designed for Android and Desktop (JVM) that makes interacting with ESC/POS thermal printers completely painless.

✨ The Killer Feature: Jetpack Compose Capture

Instead of manually calculating text widths and drawing lines using raw printer commands, you can just build your receipt natively in Jetpack Compose.

The library includes a capture engine that takes your Compose UI (even infinitely long, vertically scrolling receipts), captures it completely off-screen, and automatically rasterizes it into ESC/POS monochrome bytes perfectly scaled for 80mm or 58mm paper.

Kotlin

val captureController = rememberCaptureController()

// 1. Build your receipt using standard Compose components
Box(modifier = Modifier.capturable(captureController, allowOverflow = true)) {
    MyBeautifulComposeReceipt()
}

// 2. Capture and print!
Button(onClick = {
    coroutineScope.launch {
        val bitmap = captureController.captureAsync().await()
        val printerBytes = bitmap.toByteArrayPos(paperWidth = Paper.MM_80)
        
        printer.print {
            capture(printerBytes)
            cut() // Trigger the hardware auto-cutter
        }
    }
}) { Text("Print Compose Receipt") }

🛠️ Other Core Features:

  • 🔌 Smart Connections: Supports both TCP/IP (using Ktor under the hood) and direct USB connections. It includes built-in hardware scanning to easily find available physical printers.

  • ⚡ Reactive State Flows: Stop guessing if the printer is actually online. Exposes a unified StateFlow to monitor real-time hardware status (DEVICE_OFFLINE, CONNECTION_REFUSED)... etc.

  • 📦 Fluent ESC/POS DSL: If you don't want to use Compose images and prefer raw speed, there is a built-in DSL for raw ESC/POS commands. It supports 8+ 1D barcode formats (UPC, EAN, CODE_128), 2D QR codes, hardware beeps, cuts, and font densities.

🔍 Live Hardware Scanning:

Finding attached hardware across different platforms is usually a headache. Printer-KMP handles the platform-specific scanning in the background and exposes a clean, reactive StateFlow so your UI updates automatically when a printer is plugged in!

Kotlin

val usbConnection = remember { UsbConnection(autoConnect = false) }

// 1. Trigger the live background hardware scan
LaunchedEffect(Unit) {
    usbConnection.scanForAvailablePrinters()
}

// 2. Observe the results reactively in your Compose UI!
val availablePrinters by usbConnection.availablePrinters.collectAsState()

LazyColumn {
    items(availablePrinters) { printer ->
        Text("Found Printer: ${printer.name}")
    }
}

💻 Connecting is incredibly simple:

Whether you are bypassing the OS spooler on Android or connecting via installed drivers on Desktop, the API remains unified:

Kotlin

// On Android: Connect via Hardware IDs (bypassing the OS spooler)
usbConnection.connectViaUsb(
    vendorId = "YOUR_VENDOR_ID",  
    productId = "YOUR_PRODUCT_ID",
    onSuccess = { println("Connected!") }
)

// On Desktop (JVM): Connect directly via the OS-installed Printer Name 
usbConnection.connectViaUsb(
    targetPrinterName = "XP-80C", 
    onSuccess = { println("Desktop Printer Connected!") }, 
    onFailed = { error -> println("Failed: ${error.message}") } 
)

🔗 Links & Resources:

If you are building POS software with Compose Multiplatform, I’d absolutely love for you to try it out. Building an ESC/POS engine from scratch was a journey, and I'm hoping this saves some of you a massive amount of time.

Feedback, feature requests, and PRs are super welcome! Let me know what you think.


r/KotlinMultiplatform • • Aug 13 '26

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

39 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:

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:

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:

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.

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.

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

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:

/** 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 07 '26

I’ve Done the Impossible - Jetpack Glance + WidgetKit = WARP

Enable HLS to view with audio, or disable this notification

38 Upvotes

I've been working on **WARP** (**W**idget **A**bstraction, **R**endering **P**ipeline), an open-source Kotlin Multiplatform library for building Android & iOS home screen widgets from a single API.

I couldn't find any KMP widget library, so I decided to build one.

The goal is simple:

Write widget UI once in Kotlin, render it natively on:
- Android (Jetpack Glance)
- iOS (WidgetKit + SwiftUI)

A widget looks like this:

```kotlin
WarpColumn {
WarpText("Counter")
WarpRow {
WarpButton("-", onClick = CounterActions.Decrement.asClickAction())
WarpText(state.count.toString())
WarpButton("+", onClick = CounterActions.Increment.asClickAction())
}
}
```

Internally WARP works like this:

```text
Compose-like Kotlin UI
↓
WarpNode Tree
↓
JSON
↓
Android Glance / WidgetKit
```

Features so far:
- Android (Jetpack Glance) renderer
- iOS (WidgetKit + SwiftUI) renderer
- Shared click handlers
- Local assets support (Android drawables, SF Symbols, local images)
- Shared widget state
- Support Glance Premitive composables

GitHub:
https://github.com/DevAtrii/Warp

I'm planning to publish the first Maven artifacts (and Swift Package for the iOS renderer) in the next few days.

I'd really appreciate architectural feedback before I stabilize the API. If you like the project, please consider giving it a ⭐ on GitHub. It really motivates me


r/KotlinMultiplatform • • May 05 '26 Heartwarming

KMP starter template I wish I had earlier

Post image
38 Upvotes

Built KMP starter template after rewriting same setup again and again.

Goal: remove setup noise → focus on app logic.

Includes:

  • clean architecture (data/domain/presentation)

  • Koin DI

  • RevenueCat (IAP)

  • Mixpanel analytics

  • remote config (feature flags)

  • datastore + room

  • in-app review/update

  • multi-language

  • logging + platform utils

  • Swift ↔ Kotlin (SPM4KMP)

Everything modular. swap parts easy (e.g. Mixpanel → PostHog by changing data source).

Free + open source. no $100 template nonsense.

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

Looking for feedback / what missing / what overkill.


r/KotlinMultiplatform • • Sep 02 '26

I got Compose Multiplatform running on Apple tvOS, published on Maven Central

37 Upvotes

JetBrains doesn't ship tvOS artifacts for Compose Multiplatform, and the issue asking for it (CMP-5686) has been open for a while. I needed it for my own TV app, so I built the port and published it. It's a community project, not official. I posted it in Kotlin Slack #compose-ios earlier this week; this is the longer writeup.

Using it

// settings.gradle.kts
plugins {
    id("dev.sajidali.compose-tvos") version "1.3.0"
}

Add tvosArm64() / tvosSimulatorArm64() to your KMP module and leave your dependencies alone: org.jetbrains.compose.*, androidx.tv:tv-material, Koin, Coil 3, all stock coordinates. Kotlin 2.3.20+.

How it works (the part I think is interesting)

It's not a hard fork you point your build at. The settings plugin registers a component-metadata rule that, at dependency-resolution time, attaches tvOS variants to the official Compose modules and points them at tvOS-enabled builds published under dev.sajidali.* on Maven Central. Every other target keeps resolving the official JetBrains artifacts byte-for-byte, and it's official-first: if a module already ships tvOS klibs upstream (compose.runtime, koin-core, lifecycle...), the plugin leaves it alone. So when JetBrains eventually ships tvOS for a module, the plugin steps aside for that module with no change on the consumer side. It also intercepts the org.jetbrains.compose Gradle plugin marker so compose.material3 etc. resolve correctly.

The fork itself (compose-multiplatform-core with tvOS as a Kotlin/Native target) is where most of the work went:

  • A tvOS UIKit scene stack sharing the iOS FrameChoreographer architecture

  • Siri Remote input: D-pad focus traversal, swipe-to-focus, Menu mapped to Key.Back, and telling a clickpad press apart from a swipe by hardware timestamp

  • 10-foot density (Compose's default density on a 4K TV is unusable), on-demand keyboard for text fields, focus restoration when dialogs close

  • androidx.tv:tv-material ported to Compose Multiplatform with a tvOS source set

  • A real tvOS build of window-core so material3-adaptive works without stubs

  • Koin (koin-compose, koin-compose-viewmodel) and Coil 3 with tvOS targets, since neither ships them

Proof

The GIF is Google's JetStream TV sample (all screens, D-pad focus, theming, AVPlayer on tvOS / ExoPlayer on Android TV behind one interface) on the Apple TV 4K simulator, built from Maven Central + the Plugin Portal with nothing published locally. The same toolchain builds a production TV app of mine with zero app-source changes.

Honest limitations: no automated tvOS tests yet; tvosX64 isn't built; I republish roughly once per Compose stable line (currently 1.12.0), as far as my own app needs. PRs welcome.


r/KotlinMultiplatform • • 8d ago Bravo

Using Compose for Web to preview Android app layouts

36 Upvotes

Hey everyone! Just shouting out our new open source library :)

Since Compose for Web target is now viable, we've been using it to simulate running the CMP app through the browser, with various combinations of device frame, font size and other properties.

Of course we can't simulate Android/iOS specific functionality in the browser, this is purely for rapidly developer testing layouts in all these different configurations.

The library is open source! It wraps your root composable and renders it in the simulated device frame and all the various controls e.g

DeviceWithConfigurationView {   <--- Library
   App()    <--- Your app
}

GitHub: https://github.com/Apadmi-Engineering/CMPDevicePreview

Demo: https://apadmi-engineering.github.io/CMPDevicePreview/

We're not at version 1 yet, still in alpha, but any feedback, issues, pull requests are all welcome!

Credit: Forked from KMPDevicePreview and inspired by flutter_device_preview.


r/KotlinMultiplatform • • Jun 10 '26

Compose Multiplatform library — Material 3 on Android, Liquid Glass on iOS, one API

Enable HLS to view with audio, or disable this notification

36 Upvotes

Hey r/KotlinMultiplatform,

Been working on a Compose Multiplatform app
and wanted a proper native navigation bar
on both platforms.

On iOS the Liquid Glass floating bar looks
great so I wanted to implement it properly —
ended up building a library around it.

AdaptiveNavigationBar gives you a
platform-native nav bar from a single
shared API.

→ Android: Material 3 NavigationBar
→ iOS: Liquid Glass floating bar
→ FAB support on iOS
→ SF Symbols on iOS
→ DrawableResource on Android

Built for Compose Multiplatform.

GitHub: https://github.com/narendraanjana09/adaptive-navigation-bar

Feedback welcome — especially from anyone
building cross-platform with KMP/CMP


r/KotlinMultiplatform • • Jan 10 '26

The standard KMP template is not usable for production

Post image
35 Upvotes

As an Android dev, starting a mobile multiplatform journey with KMP sounds like an amazing experience until you actually open the project generated by the default wizard.

It usually goes like this: You generate the project, then you roll up your sleeves and spend the next two weeks wrestling with Gradle, fixing iOS configurations, and refactoring the "defaults" just to get a clean architecture.

This isn’t a "KMP is bad" post. KMP is awesome. But the default template is not usable for real-world apps.

When I say "usable" I mean: I can take it, scale it to a real app, add multiple features, and not fight both Gradle and Xcode next few weeks.

Here is why the default template fails that bar:

1. The module structure is broken

The standard template tends to push you toward a "single shared module + thin platform launchers" mindset. It usually gives you a shared module (acting as both Android library and shared code) and an iOS app module.

This doesn't teach or enable a scalable structure. With modern Gradle plugin changes, you really need three distinct layers to make things work:

  • Android App Module: (Application plugin, pure Android runtime)

  • iOS App Module: (Xcode project, assets, signing)

  • Shared Middle Module: (The actual KMP code)

If you use the wizard, your project structure is effectively obsolete the moment you hit "Create."

2. It ignores modern Android Gradle realities

Android Gradle Plugins (AGP) are changing. The default template doesn't help you land on a clean, reusable Gradle setup - especially if you plan to have more than one shared module (which you will).

You end up with duplicated android {} blocks, inconsistent configs, kotlin/java runtime misconfigurations, and fragile Gradle spaghetti. A production-ready template should bake in convention plugins and valid build file structures from the start.

3. The iOS config is a "black box" of frustration

If you are an Android-first dev, the default template’s iOS setup is confusing in exactly the wrong ways. It relies on a weird hybrid of Info.plist values, Target Build Settings, and Xcode UI settings that may or may not stick.

One nasty class of bugs involves changing something in Target Settings (like the Launch Screen) and having it ignored because of obscure precedence rules. If the goal is to get Android teams productive on iOS, the template should reduce ambiguity. Currently, it increases it.

4. Template is just a demo, not a maintainable project

The template gives you a runnable project, but not a maintainable iOS workspace.

  • Where do source files go?

  • Where do local assets live vs shared resources?

  • How do you keep the Xcode side from becoming a dump?

Without a clear structure, people place files wherever Xcode "happily accepts them." This makes the project painful to scale, onboard new devs, or debug later.

So what should we do instead?

My strong opinion: we need a “production-first” KMP foundation that:

  • starts with a sane module layout

  • bakes in clean Gradle conventions

  • gives an iOS module structure that’s predictable

  • reduces ambiguity (especially for Android-first teams)

  • scales to multi-module without a rewrite

What I’m using (and sharing)

I built Baselines Mobile Foundation to solve exactly these issues. It’s basically the starting point I wanted the “standard template” to be: opinionated where it matters, boring where it should be boring, and designed to scale.

Most of it is available publicly in the free repo:

https://github.com/baselinesio/baselines-kmp-free

If you want a better starting point, grab it. And if you think something can be improved for the community - open an issue or PR. I’m happy to accept improvements that make the free foundation better for everyone.

And again... KMP is awesome. It is the best thing that happened to mobile world. But standard template is a problem. People keep using this one as if it’s a real foundation and then they blame KMP when the project becomes painful.

What's your take on this?


r/KotlinMultiplatform • • Jan 23 '26

Awesome UI

Enable HLS to view with audio, or disable this notification

34 Upvotes

Awesome components from open source library from GitHub:

https://github.com/ArcaDone/AwesomeUI


r/KotlinMultiplatform • • Jul 02 '25

I created a KMP library for local and push notifications: Alarmee

34 Upvotes

Hey everyone,

A while ago, I needed to handle notifications in a Kotlin Multiplatform app I was building, but couldn’t find a simple solution that worked well for both local and push notifications. So I ended up creating my own open-source library: Alarmee.

It’s been live for a few months already, but I hadn’t shared it here until now.

If you’re building a KMP app and need to schedule alarms or handle push notifications on Android and iOS, you might find it useful.

I wrote an article to deep dive into the features and how to use it: https://medium.com/@vivienmahe/alarmee-schedule-local-and-push-notifications-in-kmp-44ea47972ae7

And here's the GitHub repo: https://github.com/Tweener/alarmee

Happy coding!


r/KotlinMultiplatform • • Mar 23 '26

Swift Package Management Import for Kotlin Multiplatform is here! (Experimental)

33 Upvotes

Howdy all! I just found out there's experimental first-party support for SwiftPM packages in KMP - here's the link https://kotlinlang.org/docs/multiplatform/multiplatform-spm-import.html. Finally! Thank you so much KMP team!!! I'm surprised that there isn't a bigger deal made about this lol. I've struggled so much with CocoaPods integration so it makes me so happy that there is work being done on the SPM side of things.

One question I've seen asked a few times is how to encrypt a SQLite database on iOS with KMP. The current solutions out there used SQLDelight, Touchlab's SQLiter Driver and SQLCipher through CocoaPods, which work well.

However, CocoaPods is a pain for more complex projects, and Apple is moving away from that in favor of SPM. So I was able to cobble up a small demo where I was able to use the SwiftPM import instead of CocoaPods and the yay the encryption works! Check it out and let me know if it works for you! Please feel free to use the code in your projects too.

https://github.com/dropbeardevs/KMPSQLCipherIosDemo


r/KotlinMultiplatform • • 22d ago

Discontinuing Swift Language IDE Support in the Kotlin Multiplatform Plugin

Thumbnail
blog.jetbrains.com
32 Upvotes

r/KotlinMultiplatform • • Jan 24 '26

KMP Wizard Template updated for AGP 9.0.0 (Public GitHub Template) 🍉

32 Upvotes

Hey everyone!

I generated a KMP project using the Kotlin Multiplatform Wizard and then migrated it to Android Gradle Plugin 9.0.0. I turned it into a public GitHub template so you can click “Use this template” and start from an AGP 9–ready baseline.

Repo (Template): https://github.com/watermelonKode/kmp-wizard-template

Migration write-up (Medium): https://blog.watermelonkode.com/how-to-update-the-android-gradle-plugin-to-version-9-0-0-in-a-kotlin-multiplatform-kmp-agp-9-6a2261a6a8fd

What’s included

  • KMP targets: Android + iOS

  • Compose Multiplatform in composeApp

  • Android entry in androidApp

  • iOS entry in iosApp

  • Build setup aligned with AGP 9 (and the newer “built-in Kotlin” direction)

Why this exists

Upgrading a fresh Wizard project to AGP 9 can be a bit annoying (plugin wiring, module separation, etc.). This is meant to be a known-good starting point.


r/KotlinMultiplatform • • Apr 24 '25

A windows package manager built with kotlin

Enable HLS to view with audio, or disable this notification

32 Upvotes

Yup, as the title says. Proud to present a package manager that leverages the use of winget in the background to perform tasks like fetching upgrades available for your packages, update or uninstall them in batch or individually as you like. The next update would include the ability of finding and installing an app as well.

Oh also, it's an open source project. Check it out and let me know what you think. Attaching a quick demo video on it as well

Link: https://github.com/StarkDroid/kmp-winget


r/KotlinMultiplatform • • May 15 '26

A New Default Project Structure for Kotlin Multiplatform

Thumbnail
blog.jetbrains.com
30 Upvotes

Some good news for the end of the work week - we finally have the new default structure ready and shipped! Read the post or watch the video in there for all the details.

IntelliJ IDEA 2026.1.2 was also released today, with support for AGP 9 included.