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.

Apache 2.0 · com.vocabloot:widgetbridge · 0.2.0

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

libs.versions.toml
widgetbridge = { module = "com.vocabloot:widgetbridge", version = "0.2.0" }
Package.swift, on the widget extension and the app target
.package(url: "https://github.com/vaazh-studios/widgetbridge", from: "0.2.0")
Publishing from the app (commonMain)
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 KotlinandroidWidgetBridgeiosWidgetBridge, via an App Group
ReadKotlin, bridge.read()Swift, WidgetFeedReader
ImagesWidgetImages and AssetBudgetThe same
RedrawACTION_APPWIDGET_UPDATE broadcastWidgetBridgeReloader
Kotlin in the widget processGlance runs in the app processNever

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.