← Back to blog

Presenting Perch

A deep link arrives as a string, and something has to decide what it means. That something is usually a chain of string comparisons no compiler checks. Rename a route, change a path, add a parameter, and the code still builds. It just stops matching, and you hear about it from someone who tapped a link that went nowhere.

Perch replaces the chain with a type.

@DeepLink("/payments/{id}")
data class PaymentDetails(val id: String)

The annotation is the whole contract. The class implements no interface of Perch’s, so whatever type your app groups its routes under stays yours. Properties named after a placeholder fill it; the rest become query parameters.

Using it

A feature module declares the links it can be entered by and applies the producer plugin. An app module applies the aggregation plugin, which walks its own dependency graph and generates a parser over every route it finds:

plugins {
    kotlin("multiplatform")
    id("dev.carcara.perch.aggregation")
}

perchAggregation {
    outputPackage.set("com.acme.app")
}
val parser = perchParser(schemes = setOf("acme"), hosts = setOf("acme.com"))

when (val route = parser.parse(url)) {
    is PaymentDetails -> navigator.push(route)
    else -> Unit
}

parse returns Any? and narrowing is yours to do, so Perch never has to know which navigator you use. It goes the other way too: toUrl renders a route back into the URL it came from.

The README has the full setup, including the mavenCentral() line that has to be in pluginManagement for the plugins to resolve.

What it refuses to build

/payments/{id} and /payments/{code} are structurally identical. A URL matching one matches the other, and a parser cannot know which to return.

Perch fails the build on that, and the check that earns its keep is the one in the aggregation plugin. Two teams each adding a route to their own module never meet until an app depends on both. Without a check at that seam both builds stay green, and the app throws on its first parser.

Perch is Apache 2.0, runs on Android, iOS, macOS and the JVM, and depends on kotlinx-serialization-core and nothing else. Code and sample on GitHub.