# Kotlin kmp-production-sample: an RSS reader that shares state across four platforms

> A JetBrains sample with a common Kotlin module, a hand-written SwiftUI interface, Compose on Android, Compose Multiplatform on desktop, and React on the web, all driven by one Redux-style store.

**Kotlin/kmp-production-sample** — This is an open-source, mobile, cross-platform application built with Kotlin Multiplatform Mobile. It's a simple RSS reader, and you can download it from the App Store and Google Play. It's been designed to demonstrate how KMM can be used in real production projects.

- Repository: https://github.com/Kotlin/kmp-production-sample
- Website: https://kotlinlang.org/lp/mobile/
- Stars: 2,283 · Forks: 262
- Language: Kotlin
- License: MIT
- Published: 2026-10-07 · Updated: 2026-10-07 · Language: en
- Canonical page: https://hysenlabs.com/projects/kotlin-kmp-production-sample

## Four applications and one shared module

The repository holds a common Kotlin Multiplatform module plus an Android project, an iOS project, and a desktop project. The Android application consumes the shared module through Gradle's multi-project mechanism. The iOS side is different in kind: the shared module compiles to a framework, and an internal integration Gradle task exposes that framework to the Xcode project that builds the app.

That asymmetry is worth pausing on, because it is the practical difference between the two platform integrations. Android shares a build; iOS shares a binary. Anything that behaves differently across the boundary, such as a file in `commonMain` that needs a platform implementation, has to be expressed as an expect declaration with an actual implementation, and the framework step is what makes those implementations visible to Swift.

The README points at the KMP project wizard with Android, iOS, desktop, Compose, and tests all enabled, and at a plugin for Android Studio and IntelliJ IDEA, so you can reproduce the structure without transcribing it.

The result is an RSS reader, which is a good choice for a sample. It has a network source, a local cache, an offline path, and a list with detail views, which is enough structure to be interesting without enough domain logic to be confusing. The repository description adds that the app is downloadable from the App Store and Google Play, though the README itself only links screenshots and the demo is described rather than hosted.

## Sharing data, domain, and state, but not the interface

Kotlin Multiplatform lets you choose how much to share, and the README says this sample goes further than the core layer. It shares the data layer, the domain layer, and the application state, while leaving the interface native on each platform.

The shared logic is organised into two packages:

```kotlin
com.github.jetbrains.rssreader.core
com.github.jetbrains.rssreader.app
```

The `core` package holds data and domain concerns. There are two data sources: a network service for fetching feed updates, and local storage for caching them, which is what makes the application usable offline. Ktor HTTP Client makes the requests, kotlinx.serialization handles serialising the feed data, and MultiplatformSettings stores it locally. The `app` package holds state management.

The interface layer is described as fully native, and then enumerated: SwiftUI for iOS, Jetpack Compose for Android, Compose Multiplatform for Desktop, and React.js for the web browser. Three of those four are JetBrains-owned except React, and calling React native in the same sentence as the others stretches the word, which is worth noting as you read the claim. It is accurate in the sense that matters, which is that no UI code is shared.

The trade-off this makes is the one every multiplatform project faces. Sharing state means the interesting logic is testable in one place and runs identically everywhere. Not sharing UI means four codebases to maintain and four sets of platform idioms to learn, offset by each interface feeling native rather than translated.

## A Redux store in the shared module

The state layer is a deliberately simplified Redux implementation. A `Store` class dispatches actions produced either by a user or by asynchronous work, reduces them into new state, holds the current state, and lets callers subscribe to updates through Kotlin's `StateFlow`. Alongside state, the store produces effects, which the README describes as additional information about state updates that can be used to show an alert.

Splitting effects out of state is the part that makes this design survive contact with a real application. A state change is something the interface renders; an effect is something the interface does once, such as showing a message that should not reappear when the view recomputes. Conflating them in a single state object is the usual failure mode of Redux in a UI framework with fine-grained reactivity.

StateFlow as the subscription mechanism is the other detail. It is a Kotlin coroutines primitive, so it is available identically in the common module and on every target, which is exactly the property that makes the shared layer possible without expect and actual declarations for the subscription API itself.

The README also documents dependent fetching and prefetching as further patterns, and places both in the shared module, so they are demonstrated in one place rather than separately per platform.

## The iOS integration is the part to study

If you read one part of this sample carefully, make it the iOS section, because it is where the friction of sharing state with a non-Kotlin UI framework actually shows up.

On the iOS side the store from the shared module is wrapped into an `ObservableObject`, with the state exposed as a `@Published` property. That publishes changes whenever a dispatched action has been reduced in the shared module into new state. The store is then injected as an `Environment Object` into the root view, which makes it reachable from anywhere in the application.

That is a small amount of glue, and it is the whole integration. SwiftUI then does all of its own diffing on the render pass when the state changes, so nothing about the reactivity model leaks into the view code.

For subscription, the README points at a thin wrapper in the shared iOS source set, at `shared/src/iosMain/kotlin/com/github/jetbrains/rssreader/core/CFlow.kt`. Its job is narrow: it lets you provide a callback that fires on each new value emitted by the flow, which is the bridge from Kotlin's coroutines API to a Swift closure. Platform-specific plumbing like this living in `iosMain` rather than in the shared `commonMain` package is the pattern to copy.

The Android counterpart is referenced in the same style, at `shared/src/androidMain/kotlin/com/github/jetbrains/rssreader/core/RssReader.kt`, for the factory method described below.

## expect and actual for something the standard library lacks

The multiplatform features section gives one concrete example, and it is a good one because the limitation is real rather than contrived. RSS feeds are XML, and kotlinx.serialization does not parse XML. Rather than writing a parser in the shared module, the sample declares a common `FeedParser` interface in the `commonMain` source set and puts the implementations in the platform source sets: `XmlPullParser` on Android, `NSXMLParser` on iOS.

The implementations are then injected into the `RSSReader` class, which the README calls the module entry point, through a `create` factory method declared in that class's companion object. So the shape is the standard expect and actual pattern with one addition: rather than the platform picking its own implementation, the shared code takes it as a parameter at construction.

That is worth understanding because it moves the decision. With plain expect and actual, the platform binding happens at link time and the shared code cannot substitute an implementation. With an explicit factory, you can pass a fake parser in a test, which is what makes the data layer testable without a device. For a sample whose purpose is to demonstrate a production arrangement, choosing the more testable of two equally standard mechanisms is a deliberate signal.

The desktop and web applications are described in the same architecture but get less detail in the README, and their entry points are listed as separate apps in the repository tree rather than explained.

## Repository naming, tags, and how current this is

Two inconsistencies are worth knowing about, because both will cost you time if you follow the links.

The first is naming. The repository is `Kotlin/kmp-production-sample` and the README heading calls it KMP RSS Reader. But the source links embedded in the README point at `github.com/Kotlin/kmm-production-sample/blob/master/shared/src/...`, using the older KMM name. The files exist in this repository at those paths, so the links resolve only by redirect, if at all. The README itself mixes both vocabularies freely, calling the shared layer a KMM module in several places while using Kotlin Multiplatform in others.

The second is versioning. There are exactly two tags and neither is a version number. `desktop_web`, from 2021-12-30, added experimental Compose Desktop and React web applications. `compose-app`, from 2023-03-15, added an experimental Compose Multiplatform application. Both are milestone markers rather than releases.

Against that, the last push was 2026-09-18, so the code is moving. The gap between a 2023 tag and a 2026 push date is the honest summary of this sample's currency: the architecture it documents still holds, but specific details such as which platform library is used for a given job have had three years to move underneath it.

The license is MIT. It sits at 2,283 stars and 262 forks with 9 open issues, and the topics list includes both `kmm` and `kotlin-multiplatform` alongside `reactjs`, which is an accurate summary of the vocabulary problem in the README itself.

## Conclusion

What makes this sample worth reading is that it refuses the easy version of the multiplatform story. It does not put a Compose UI in the shared module and call it shared. It shares the data layer, the domain layer, and the application state, then writes each interface in the idiom of its platform, including a genuine SwiftUI integration that wraps the store as an `ObservableObject` and hands it to the view tree as an environment object. That choice is what makes the architecture diagram meaningful and what makes the sample usable as a reference rather than as a toy. Two caveats before you treat it as current. The repository name and the README disagree with each other, since the source links inside the README point at `kmm-production-sample` while the repository is `kmp-production-sample`, and the newest tagged release is from 2023 even though the code has been pushed as recently as September 2026. The composition of the shared module has moved on since those tags.

## FAQ

### What is the difference between KMP and CMP?

KMP is Kotlin Multiplatform, the technology for sharing Kotlin code across targets, and CMP is Compose Multiplatform, JetBrains' multiplatform version of Compose. They are not alternatives. You can share plain Kotlin with KMP and write each interface natively, or you can additionally share a Compose-based interface, and this sample deliberately does the former and uses Compose Multiplatform only on desktop.

### What is KMM vs KMP?

KMM was the older name, standing for Kotlin Multiplatform Mobile, used when sharing was primarily about mobile targets. KMP is the current name for Kotlin Multiplatform after it extended beyond mobile. The sample carries both: its repository is named kmp-production-sample while the README's own source links use kmm-production-sample and its prose calls the shared layer a KMM module in places.

### Which layers does the KMP production sample share?

The data layer, the domain layer, and the application state. State management uses a simplified Redux store in the shared module, dispatching actions and exposing updates through StateFlow, with effects kept separate from state. The interface layer is not shared: SwiftUI on iOS, Jetpack Compose on Android, Compose Multiplatform on desktop, React on the web.

### How does the shared module connect to the iOS app?

It compiles to a framework that an internal integration Gradle task exposes to the Xcode project. On the Swift side the store is wrapped in an ObservableObject with state exposed as a @Published property, and injected into the root view as an environment object. Subscriptions go through a small callback wrapper that lives in the iOS source set of the shared module.

### How does the sample handle XML parsing with kotlinx.serialization?

It declares a common FeedParser interface in the commonMain source set and implements it per platform, using XmlPullParser on Android and NSXMLParser on iOS, since kotlinx.serialization does not parse XML. The implementations are passed into the RSSReader class through a create factory method in its companion object, which also lets a test supply its own parser.

## Sources

- [Kotlin/kmp-production-sample on GitHub](https://github.com/Kotlin/kmp-production-sample)
- [License: MIT](https://github.com/Kotlin/kmp-production-sample/blob/master/LICENSE)
- [Project website](https://kotlinlang.org/lp/mobile/)
- [README](https://github.com/Kotlin/kmp-production-sample/blob/master/README.md)
- [Releases](https://github.com/Kotlin/kmp-production-sample/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/kotlin-kmp-production-sample
