WidgetBridge
Typed, atomic handoff of data and images from a Kotlin Multiplatform app to its home-screen widgets: Glance on Android, WidgetKit on iOS. The widget extension never links Kotlin.
Why it exists
Widgets cannot run your shared Kotlin. An iOS widget is a separate extension with a memory ceiling around 30 MB: link the Kotlin framework into it and it gets killed, and App Store validation rejects frameworks nested in extensions anyway. On Android, Glance widgets wake while the app is not running. So every Kotlin Multiplatform app hand-rolls the same handoff, usually a JSON string in UserDefaults, with no atomicity across files, no images, and no recovery from a corrupt write.
What it does
- Typed payload. Your @Serializable class goes in, and the same shape comes out in Kotlin and as Decodable in Swift.
- Atomic generations with a fallback. Written completely, renamed into place, pointer switched. A reader falls back to the previous generation when the current one is corrupt or has the wrong schema.
- Images included. Downsampled PNG or JPEG assets under a byte budget, resolved with path-safety checks.
- Dedupe and redraw. Unchanged content writes nothing. Changed content asks the OS to redraw, on both platforms.
- The same rotation on both platforms. Hourly slot maths with test vectors shared between the Kotlin and the Swift side.
- Small. Kotlin needs coroutines and kotlinx-serialization. Swift needs Foundation. No Glance dependency, no DI, no UI.
Getting started
widgetbridge = { module = "com.vocabloot:widgetbridge", version = "0.2.0" }
.package(url: "https://github.com/vaazh-studios/widgetbridge", from: "0.2.0")
val bridge = widgetBridge() // androidWidgetBridge(context) / iosWidgetBridge(appGroup)
bridge.publish(
payload = WordOfTheDay(word = "hola", meaning = "hello"),
images = listOf(WidgetImage("hero", bytes)),
)
// Written whole, renamed into place, pointer flipped, then the OS is asked to redraw.
On iOS the widget extension adds a ten-line Swift package and an App Group. It never links the Kotlin framework, which is the point of the library.
Support matrix
| Android (Glance) | iOS (WidgetKit) | |
|---|---|---|
| Publish from Kotlin | androidWidgetBridge | iosWidgetBridge, via an App Group |
| Read | Kotlin, bridge.read() | Swift, WidgetFeedReader |
| Images | WidgetImages and AssetBudget | The same |
| Redraw | ACTION_APPWIDGET_UPDATE broadcast | WidgetBridgeReloader |
| Kotlin in the widget process | Glance runs in the app process | Never |
Kotlin 2.3, Gradle 9.0, AGP 9.0, Android minSdk 24 with Glance 1.1 in your app, iOS 16 and Xcode 16 or newer, Swift tools 5.9.
Who is using it
Vocabloot, on both platforms. The iOS widget it drives has never linked the shared Kotlin framework, which is what the library exists to make ordinary rather than clever.
Everything else
- Source on GitHub issues, discussions and the samples
- Documentation setup, the feed format, recipes, FAQ and known issues
- API reference generated from the source by Dokka
- Maven Central every published version