BikeShare: one Kotlin core, four UI targets and a public API
SwiftUI, Jetpack Compose, Compose for Desktop and Compose for Web based Kotlin Multiplatform project (using CityBikes API http://api.citybik.es/v2/). Uses Room for local persistence
At a glance
- What is it?
- BikeShare is a Kotlin Multiplatform sample project that reads the CityBikes API and renders it with SwiftUI on iOS, Compose on Android, desktop and the browser, plus a Swift executable package. It is a reference implementation to read, with Circuit, kotlin-inject, Room and a shared Maestro flow.
- Who is it for?
- Adopt BikeShare as a reference when you are starting a Kotlin Multiplatform project that has to serve both SwiftUI and Compose clients, since it shows how one shared core is wired to a Swift interface, a Compose interface and a command line target with a single dependency injection setup. Do not adopt it as a production dependency, because the repository publishes no GitHub releases and it is written to demonstrate patterns rather than to be consumed as a library.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly Jupyter Notebook, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
One shared core, a SwiftUI shell and a Compose shell
BikeShare is a Kotlin Multiplatform sample project built on the CityBikes API at api.citybik.es, the public feed of bike sharing station availability. It is described as Compose Multiplatform and SwiftUI based, and the sample targets are listed as iOS with SwiftUI, Android with Jetpack Compose, Desktop with Compose for Desktop, the Web with Wasm based Compose for Web, and a Swift Executable Package.
The important structural point is that the UI is not shared, only the logic is. There is one Kotlin core that talks to the API and holds the state, and then two different ways of presenting it: a native Swift interface on Apple platforms and a Compose interface everywhere else. That is a deliberate choice for a sample, and it is the pattern most teams reach for when they want a native feel on iOS without giving up a single codebase.
The topic list adds the technical detail. The project carries tags for ktor and kotlinx-serialization, so networking and parsing live in the shared core, along with kotlin-coroutines, kotlin-native, wasm, jetpack-room for persistence, mvvm and viewmodel for the presentation layer, and kotlin-inject and Circuit for dependency injection and navigation. The description notes that Room is used for local persistence.
Circuit and kotlin-inject replace hand-written wiring
The README calls out two libraries as the backbone: Circuit, which comes from the Slack engineering organisation, and kotlin-inject. Both are worth understanding before reading the code, because they determine where the seams are.
kotlin-inject is a compile time dependency injection framework, so the object graph is generated rather than assembled at runtime. In a multiplatform project that matters more than usual, since a runtime reflection based injector would need an implementation for every target, including the browser and the Wasm build. Circuit sits above that and provides the navigation and screen composition layer, giving the Compose targets a way to describe screens and their dependencies in one place.
The author also writes about the combination in the linked posts, including one on using kotlin-inject in a Kotlin and Compose Multiplatform project and another on using Circuit with kotlin-inject in the same setting. For a reader deciding whether the structure is worth copying, those posts are the more detailed source, since the README only names the libraries.
Running the same app on four targets
Each target has its own Gradle task, and the README lists them plainly:
./gradlew :androidApp:installDebug
./gradlew :compose-desktop:run
./gradlew :compose-web:wasmJsBrowserDevelopmentRunThe Android task installs the debug build on a connected device or emulator, and the desktop task runs the Compose for Desktop application locally. The web task is the one to look at twice, because the Wasm variant compiles to WebAssembly and runs in the browser rather than to JavaScript. The repository carries a kotlin-js-store directory for the JavaScript dependency lock file and a sqlite-wasm-worker directory, which is where the browser build gets its storage worker.
iOS is the odd one out, because Apple builds do not go through Gradle. The instructions are to open ios/BikeShare/BikeShare.xcodeproj in Xcode and run from there, with the ios-export/ directory holding the exported framework that the Xcode project consumes.
There is a fifth target that has no GUI at all: the Swift Executable Package in SwiftExecutablePackage/, which the README lists among the supported runtimes. A command line target built from the shared core is the cheapest way to check that your business logic compiles and behaves outside the app, and this sample keeps one.
A single Maestro flow shared by Android and iOS
The test setup is one of the more instructive parts of the repository. There is a Maestro UI test flow shared by the Android and iOS clients, run in CI on an emulator and a simulator:
maestro test -e APP_ID=dev.johnoreilly.bikeshare maestro/BikeShare.flowThe flow lives in the maestro/ directory and is passed an application identifier through an environment variable, so the same script drives both clients. That is the payoff of keeping the two user interfaces behaviourally aligned: instead of writing one flow per platform, you write one flow and run it twice against different binaries.
The iOS side also has fastlane/ and the ios-export/ directory, which point to a release path as well as a development path, and the Android build is installed with installDebug, so both platforms have a defined way to get a build onto a device. The repository also carries renovate.json for dependency update automation and a .devcontainer/ directory for a reproducible development environment, plus a .junie/ directory for tooling configuration.
The repository layout as an architecture map
The directory names tell you where the boundaries are. common/ holds the shared Kotlin code, the part that runs on every target. androidApp/ is the Android application module, compose-desktop/ and compose-web/ are the two Compose targets, ios/ holds the Xcode project and Swift sources, and SwiftExecutablePackage/ is the command line target. build.gradle.kts, settings.gradle.kts, gradle.properties and the gradle/ directory hold the build configuration.
Two entries tell you the project is more than a demo. The art/ directory holds the screenshots used in the README, and the gradle/ directory contains the version catalog, since the Kotlin version badge in the README is generated by querying gradle/libs.versions.toml directly. The dependency versions therefore live in one place and are read by the badge, which is a small thing that makes a repository easier to trust over time.
The other thing the layout reveals is where the effort went. There is no server component in the tree, because the data comes from a public API. The README also notes that the flag images shown in the interface come from a separate repository, which is the kind of detail a sample project is allowed to leave loose in a way a product would not.
Where BikeShare is the wrong thing to copy
There are reasons to read this project and reasons not to build on it.
The first is toolchain risk. The project requires JDK 17 or later and a recent Android Studio release with AGP 9.x support, because it uses the new com.android.kotlin.multiplatform.library plugin. That plugin is new enough that a team pinned to an older Android Gradle Plugin cannot run this build without changes, and the constraint applies to the whole repository rather than to one module.
The second is the nature of the sample. It is written to demonstrate patterns, and its own README points at a long list of related posts and a full set of sibling sample projects, which is the right way to read it and the wrong way to treat it as a maintained library. The repository publishes no GitHub releases, so there is no tagged version to depend on.
The third is the deliberate duplication of the two UI layers. If your plan was to write the interface once, this sample shows the opposite approach: a Swift interface and a Compose interface, each maintained by hand. The alternative pattern, visible in the linked post on creating a SwiftUI project that consumes shared non-UI Kotlin Multiplatform code, is a single Swift interface over a shared core, which trades Compose portability for one Apple implementation.
There is also the matter of the data source. CityBikes is a public community feed with no API key, which is why the sample needs no credentials. A production app built on the same shape would add authentication, caching and error handling that the sample deliberately leaves out.
Licence, activity and the wider set of samples
The project is under the Apache-2.0 licence, so the code can be reused in commercial and open source work under its terms, with the usual attribution and patent provisions.
On upkeep, the last push was on 2026-10-19 and the repository is not archived. There are no GitHub releases, so the history of the project is a commit history rather than a version history, and the renovate.json configuration means dependency bumps arrive as automated pull requests. The .devcontainer/ directory means a contributor can start from a container rather than assembling a JDK, an Android SDK and an Xcode toolchain by hand.
The wider context is worth knowing. The README lists a full set of Kotlin Multiplatform, Compose and SwiftUI samples by the same author, including PeopleInSpace, GalwayBus, Confetti, FantasyPremierLeague, ClimateTrace, GeminiKMP, MortyComposeKMM, StarWars, WordMasterKMP and a Chip-8 implementation. Between them they cover state flows in a multiplatform project, a NoSQL database, Swift command line apps consuming shared Kotlin code, bridging Swift 5.5 concurrency with Kotlin coroutines through KMP-NativeCoroutines, and app widgets on iOS and Android. BikeShare is the one to open when you want to see the full target matrix, but the sibling projects are where the specific techniques are demonstrated in isolation.
Editorial conclusion
Adopt BikeShare as a reference when you are starting a Kotlin Multiplatform project that has to serve both SwiftUI and Compose clients, since it shows how one shared core is wired to a Swift interface, a Compose interface and a command line target with a single dependency injection setup. Do not adopt it as a production dependency, because the repository publishes no GitHub releases and it is written to demonstrate patterns rather than to be consumed as a library. Verify three things first: that your toolchain can satisfy the requirements, namely JDK 17 or later and an Android Studio release with AGP 9.x support for the com.android.kotlin.multiplatform.library plugin; which UI framework you want to own, because the SwiftUI and Compose paths are separate implementations of the same features rather than one shared UI; and how the web target persists data, since the tree carries a sqlite-wasm-worker directory alongside Room. The licence is Apache-2.0 and the last push was on 2026-10-19.
Frequently asked questions
What is the BikeShare project?
It is a Kotlin Multiplatform sample application that reads the CityBikes API and presents bike station data with SwiftUI on iOS and with Compose on Android, Desktop and the browser. A Swift Executable Package target runs the same shared core as a command line program.
How do I run BikeShare on the desktop and in the browser?
Use ./gradlew :compose-desktop:run for the desktop target and ./gradlew :compose-web:wasmJsBrowserDevelopmentRun for the web target. The web build is Wasm based Compose for Web rather than a JavaScript build.
What do I need to build BikeShare?
JDK 17 or later and a recent Android Studio release with AGP 9.x support, because the project uses the new com.android.kotlin.multiplatform.library plugin. On iOS you open ios/BikeShare/BikeShare.xcodeproj in Xcode and run from there.
Which libraries does BikeShare use for architecture and dependency injection?
It uses Circuit from the Slack engineering organisation and kotlin-inject, with Room for local persistence and ktor with kotlinx-serialization for networking in the shared core. The README links to posts covering both libraries in a multiplatform setup.
How are the UI tests run across Android and iOS?
There is a single Maestro flow shared by both clients, run in CI on an emulator and a simulator with the command maestro test -e APP_ID=dev.johnoreilly.bikeshare maestro/BikeShare.flow.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/joreilly-bikeshare)Was this useful?