An extensible and multiplatform routing system powered by Ktor

Kotlin Routing

A multiplatform, extensible and independent “navigation” routing library powered by Ktor. Create routing independent and extend to what you need.

val router = routing {
    route(path = "/login") {
        // handle {} is generic for any routing event (pop, push, replace, replaceAll...)
        handle {
            val application = call.application
            val routeMethod = call.routeMethod (RouteMethod.Pop, RouteMethod.Push, RouteMethod.Replace, RouteMethod.ReplaceAll)
            val uri = call.uri
            val attributes = call.attributes // Always empty by default
            val parameters = call.parameters // All in one (path parameters ({...}) + routing parameters (push(parameters = ...)) + query parameters ("/path?q=search&name=routing"))
            
            // Here is your time
            // Are you on Android? Do your Intent or finish your Activity
            // Are you on Desktop? Use your Stack based navigation and navigate
            // Are you on iOS? Do your navigation like using UINavigationController
            // Are you on Web? Push, replace or pop your history state
        }
    }
}

// And to route do:
router.push(path = "/login")

Declaring routes

Based on Ktor Routing

val router = routing {
    // name is optional and used to named navigation
    route(path = "/path", name = "path") {
        handle {
            // handle any routing action to this route (pop, push, replace, ...)
        }
    }

    // name is optional and used to named navigation
    route(path = "/path2", name = "path2") {
        push {
            // handle push to this route only
        }
        replace {
            // handle replace to this route only
        }
        replaceAll {
            // handle replaceAll to this route only
        }
        pop {
            // handle pop to this route only
        }
    }

    // handle is short version to combine route { handle{} }
    handle(path = "/path3", name = "path3") {
        // handle any routing action to this route (pop, push, replace, ...)
    }

    // push is short version to combine route { push{} }
    push(path = "/path4", name = "path4") {
        // handle push to this route only
    }

    // push is short version to combine route { replace{} }
    replace(path = "/path5", name = "path5") {
        // handle replace to this route only
    }

    // push is short version to combine route { replaceAll{} }
    replaceAll(path = "/path6", name = "path6") {
        // handle replaceAll to this route only
    }

    // pop is short version to combine route { pop{} }
    // pop can not be named
    pop(path = "/path7") {
        // handle pop to this route only
    }

    // If you need redirect one route to another do:
    route|handle|push|replace|replaceAll|pop(...) {
        redirectToPath(path = "/path-destination")
        // or
        redirectToName(name = "destination-name")
    }

    // If you need a route Regex based do:
    route|handle|push|replace|replaceAll|pop(path = Regex()) {
        // ...
    }
}

Routing routes

There is no behavior in Ktor for that

val router = routing {
    // ...
}

// Pushing a path
router.push(path = "/path")

// Pushing a path with parameters
router.push(path = "/path", parameters = parametersOf("number", "123"))

// Pushing a name
router.pushNamed(name = "name")

// Pushing a name with parameters
router.pushNamed(name = "name", parameters = parametersOf("number", "123"))

// Pushing a name with parameters and path parameters ("/path/{id}")
router.pushNamed(
    name = "name",
    pathParameters = parametersOf("id", "456"), // It will be used to do a replace in the path
    parameters = parametersOf("number", "123"),
)

// Replacing or replacing all works the same as push but 'replace' instead push :D
router.replace(...)
router.replaceAll(...)

// Popping the last pushed or replaced route
router.pop()

// Popping the last pushed or replaced route with parameters
router.pop(parameters = parametersOf("number", "123"))

Type-safe routing

Based on Ktor Type-safe routing

First you have to put module resources as a dependency

@Resource("/articles")
class Articles {

    @Resource("new")
    class New(val parent: Articles = Articles())

    @Resource("{id}")
    class Id(val parent: Articles = Articles(), val id: Long) {

        @Resource("edit")
        class Edit(val parent: Id)
    }
}

val router = routing {
    install(Resources)

    push<Articles.New> {
        // handle push to this route only
    }

    replace<Articles> {
        // handle replace to this route only
    }

    replaceAll<Articles> {
        // handle replaceAll to this route only
    }

    // There is no use case for type-safe pop handler. Maybe in the future?
}

// Pushing a typed route
router.push(Articles.New())

// Replacing a typed route
router.replace(Articles())

// Pushing or Replacing a typed route with parameters
router.push|replace|replaceAll(Articles.Id(id = 123))

// Popping the last pushed or replaced route
router.pop()

Exception routing handler

Based on Ktor Status pages

First you have to put module status-pages as a dependency

val router = routing {
    install(StatusPages) {
        // Catch any exception (change to be specific if you need)
        exception<Throwable> { call, cause ->
            // exception handled during push, replace or pop routing
        }
    }
    
    push(path = "/path") {
        throw IllegalArgumentException("simulating an exception thrown on routing")
    }
}

// Pushing to simulate
router.push(path = "/path")

Next steps

[ ] – Helper functions for each platform (Android, iOS, Web, Desktop, …)

[ ] – Support query parameters from external URI as Deep Link, Browser URL, etc

[ ] – More plugins like Session, CallLogging, etc

[ ] – Samples

GitHub

View Github

Leave a Reply

Your email address will not be published. Required fields are marked *