Skip to content

joelkanyi/kepler

Repository files navigation

Kepler

A small, navigation agnostic deeplink engine for Kotlin Multiplatform.

Kepler turns a raw link, from a push notification, a QR code, a web URL, or a chat message, into a validated, intercepted, typed outcome that your app acts on. It stops before navigation, so it works with any navigation library: Compose Multiplatform Navigation, Compose Destinations, Navigation 3, Voyager, Decompose, Circuit, or your own.

Usage

Describe your destinations however you like. A sealed type keeps the handling exhaustive.

sealed interface Screen {
    data class NotificationDetail(val id: String) : Screen
    data object WhatsNew : Screen
    data class Outlet(val id: String) : Screen
}

Build a Kepler, mapping each route pattern to a typed destination. Required parameters are path segments, optional parameters are query parameters, and repeated query parameters bind to a list.

val kepler = Kepler<Screen> {
    route("notifications/{id}") { NotificationDetail(id = string("id")) }
    route("whatsnew") { WhatsNew }
    route("outlets/{id}") { Outlet(id = string("id")) }
}

Handle a link and act on the outcome.

when (val outcome = kepler.handle("app://notifications/123")) {
    is KeplerOutcome.Resolved -> navigateTo(outcome.destination)
    is KeplerOutcome.Pending -> goToLogin()
    is KeplerOutcome.Rejected -> goToHome()
    is KeplerOutcome.Proceed -> Unit
}

Interception and auth gating

Interception is modeled on OkHttp's Interceptor and Chain. Auth gating, analytics, and feature flags compose as interceptors rather than branches. AuthInterceptor defers a link until the user is authenticated and captures it for replay.

val kepler = Kepler<Screen> {
    route("notifications/{id}") { NotificationDetail(string("id")) }

    intercept(AuthInterceptor { session.isLoggedIn })

    pendingStore = keplerPendingRouteStore(context)
}

A deferred link is stored, then replayed after sign in.

kepler.resumePending()?.let { outcome ->
    if (outcome is KeplerOutcome.Resolved) navigateTo(outcome.destination)
}

Android

Feed an intent straight in. The activity overload also clears the consumed intent, so a later recreation does not replay the same link.

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    kepler.handle(this)?.let { outcome -> /* ... */ }
}

override fun onNewIntent(intent: Intent) {
    super.onNewIntent(intent)
    kepler.handle(intent)?.let { outcome -> /* ... */ }
}

Testing

Because parsing and resolution are pure Kotlin, every deeplink is testable on the JVM without an emulator. The kepler-testing artifact adds assertions.

assertThat(kepler.handle("app://notifications/123"))
    .isResolvedTo(Screen.NotificationDetail(id = "123"))

Two modes

A navigation library that already matches URIs, such as Compose Destinations, runs in Mode A: register no routes and Kepler returns Proceed, handing the link to that library's matcher after interception. A library without a matcher runs in Mode B: register routes and Kepler returns Resolved with a typed destination.

Kepler does not verify Android App Links, host links, or attribute installs. Those are OS and server concerns. See DESIGN.md for the full architecture.

Download

dependencies {
    implementation("io.github.joelkanyi:kepler:0.1.0")
    testImplementation("io.github.joelkanyi:kepler-testing:0.1.0")
}

License

Copyright 2026 Joel Kanyi

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

About

A navigation-agnostic deep link engine for Kotlin Multiplatform.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages