Skip to content

Repository files navigation

quick-actions-kmp

One API. Home-screen quick actions on iOS and Android.

Maven Central Kotlin License

badge-android badge-ios badge-macos badge-jvm badge-wasm

Publish the menu that appears when the user long-presses your app icon, and receive the tap, from commonMain. iOS uses UIApplicationShortcutItem; Android uses ShortcutManagerCompat dynamic shortcuts. The same QuickAction you published comes back in the launch, on both platforms, whether the tap started the app or reached it running.

Screenshots

iOS Home Screen iOS launch received Android launcher Android launch received
iOS quick action menu with a static item and three dynamic actions Sample app on iOS showing the action that opened it after a cold start Android launcher shortcut menu with a static item and three dynamic actions Sample app on Android showing the action that opened it after a cold start

Install

commonMain.dependencies {
    implementation("io.github.androidpoet:quick-actions:0.2.0")
    implementation("io.github.androidpoet:quick-actions-compose:0.2.0")   // Compose helpers
}

That is the whole setup. No Swift, no export, no manifest edits.

Use

@Composable
fun App() {
    val quickActions = rememberQuickActionsManager()

    // 1. Publish the menu. Re-publishing an equal list is free, so keep this at the root.
    PublishQuickActions(
        listOf(
            QuickAction("start-timer", "Start timer", subtitle = "25 minutes", icon = "timer", data = mapOf("route" to "timer")),
            QuickAction("log-water", "Log water", icon = "drop.fill", data = mapOf("route" to "water")),
        ),
        quickActions,
    )

    // 2. Receive the tap. The tap that started the app is held until this runs.
    OnQuickActionLaunch(quickActions) { launch ->
        navigate(launch.action.data["route"])
        quickActions.reportUsed(launch.action.id)
    }
}

icon is an SF Symbol name on iOS. On Android, map it to a drawable once at start-up (skip this and every action shows the app icon):

QuickActions.androidConfig = AndroidQuickActionsConfig(
    iconResolver = { key -> when (key) { "timer" -> R.drawable.ic_timer; "drop.fill" -> R.drawable.ic_drop; else -> 0 } },
)

Without Compose: IosQuickActionsManager() / AndroidQuickActionsManager(context), collect launches, and on Android call QuickActions.attach(activity) once in onCreate.

What you get

set / add / remove / clear Replace or edit the dynamic items; results are typed, never exceptions
actions StateFlow of what is published, restored from the platform
launches Every tap, as the QuickAction you published, with createdScreen and a timestamp
maxActions Free slots: the platform ceiling minus static items
reportUsed(id) Feeds Android launcher ranking; no-op on iOS

launch.createdScreen is true when the tap started the screen and false when it reached one already showing.

Platforms

Platform Surface Floor App-side setup
Android Launcher long-press menu (dynamic shortcuts) API 26 None with Compose, else QuickActions.attach(activity)
iOS Home Screen quick actions iOS 13 None
JVM, macOS, Wasm UnsupportedQuickActionsManager, so shared code compiles

On iOS the library hooks the app and scene delegates when the binary loads, so SwiftUI apps, UIKit apps with their own delegates and apps without a scene manifest all work as they are; your own delegate methods keep running. On Android, attach delivers the launch intent once per Activity lifetime, ignores relaunches from Recents and drops intents whose id is not a shortcut the platform knows for your app. Static items (Info.plist, shortcuts.xml with QuickActions.ACTION) are delivered too.

Things to know

  • Four visible slots. Both home screens show about four items including static ones. iOS rejects the fifth with TooManyActions; Android accepts more (maxActions is usually 14) and hides the rest.
  • Android long label is title · subtitle, because launchers show the long label when it fits.
  • data is untrusted. It travels through an exported Activity. Use it to pick a route; never run it.
  • One collector. Each launch reaches exactly one collector of launches. Collect at the root.
  • Rate limiting. Android refuses shortcut changes from a backgrounded app; you get RateLimited.

Full reference, Android options and the iOS hook details: https://androidpoet.github.io/quick-actions-kmp/

Sample

sample/composeApp is one shared screen: pick actions, publish, and watch launches arrive. Android: ./gradlew :sample:composeApp:installDebug. iOS: cd sample/composeApp/iosApp && xcodegen && open iosApp.xcodeproj.

License

MIT © Ranbir Singh

About

Home-screen quick actions for Kotlin Multiplatform: UIApplicationShortcutItem on iOS, ShortcutManagerCompat dynamic shortcuts on Android, one common API

Topics

Resources

Code of conduct

Stars

12 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages