Skip to content

Commit

Permalink
Merge pull request #46 from hotwired/navigation-decision-handler
Browse files Browse the repository at this point in the history
Rename Route -> RouteDecisionHandler
  • Loading branch information
jayohms authored May 22, 2024
2 parents 8806d71 + 4019f0c commit 0fca5c2
Show file tree
Hide file tree
Showing 11 changed files with 65 additions and 64 deletions.
14 changes: 7 additions & 7 deletions core/src/main/kotlin/dev/hotwire/core/config/Hotwire.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import dev.hotwire.core.bridge.BridgeComponent
import dev.hotwire.core.bridge.BridgeComponentFactory
import dev.hotwire.core.navigation.fragments.HotwireWebBottomSheetFragment
import dev.hotwire.core.navigation.fragments.HotwireWebFragment
import dev.hotwire.core.navigation.routing.AppNavigationRoute
import dev.hotwire.core.navigation.routing.BrowserRoute
import dev.hotwire.core.navigation.routing.AppNavigationRouteDecisionHandler
import dev.hotwire.core.navigation.routing.BrowserRouteDecisionHandler
import dev.hotwire.core.navigation.routing.Router
import dev.hotwire.core.turbo.config.PathConfiguration
import kotlin.reflect.KClass
Expand All @@ -25,8 +25,8 @@ object Hotwire {
private set

internal var router = Router(listOf(
AppNavigationRoute(),
BrowserRoute()
AppNavigationRouteDecisionHandler(),
BrowserRouteDecisionHandler()
))

val config: HotwireConfig = HotwireConfig()
Expand All @@ -45,11 +45,11 @@ object Hotwire {
}

/**
* Registers the [Router.Route] instances that determine whether to route location
* Registers the [Router.RouteDecisionHandler] instances that determine whether to route location
* urls within in-app navigation or with alternative custom behaviors.
*/
fun registerRoutes(routes: List<Router.Route>) {
router = Router(routes)
fun registerRouteDecisionHandlers(decisionHandlers: List<Router.RouteDecisionHandler>) {
router = Router(decisionHandlers)
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ class Navigator(
extras: FragmentNavigator.Extras? = null
) {

if (getRouteResult(location) == Router.RouteResult.STOP) {
if (getRouteDecision(location) == Router.Decision.CANCEL) {
return
}

Expand Down Expand Up @@ -350,15 +350,15 @@ class Navigator(
return currentDestination.navigatorForNavigation(location).navController
}

private fun getRouteResult(location: String): Router.RouteResult {
val result = currentDestination.route(location)
private fun getRouteDecision(location: String): Router.Decision {
val decision = currentDestination.decideRoute(location)

logEvent(
"routeResult",
"routeDecision",
"location" to location,
"result" to result
"decision" to decision
)
return result
return decision
}

private fun navOptions(location: String, action: VisitAction): NavOptions {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import androidx.core.net.toUri
import dev.hotwire.core.navigation.activities.HotwireActivity
import dev.hotwire.core.navigation.navigator.NavigatorConfiguration

class AppNavigationRoute : Router.Route {
class AppNavigationRouteDecisionHandler : Router.RouteDecisionHandler {
override val name = "app-navigation"

override val result = Router.RouteResult.NAVIGATE
override val decision = Router.Decision.NAVIGATE

override fun matches(
location: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import dev.hotwire.core.lib.logging.logError
import dev.hotwire.core.navigation.activities.HotwireActivity
import dev.hotwire.core.navigation.navigator.NavigatorConfiguration

class BrowserRoute : Router.Route {
class BrowserRouteDecisionHandler : Router.RouteDecisionHandler {
override val name = "browser"

override val result = Router.RouteResult.STOP
override val decision = Router.Decision.CANCEL

override fun matches(
location: String,
Expand All @@ -29,7 +29,7 @@ class BrowserRoute : Router.Route {
try {
activity.startActivity(intent)
} catch (e: ActivityNotFoundException) {
logError("BrowserRoute", e)
logError("BrowserRouteDecisionHandler", e)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import dev.hotwire.core.navigation.activities.HotwireActivity
import dev.hotwire.core.navigation.navigator.NavigatorConfiguration
import dev.hotwire.core.turbo.util.colorFromThemeAttr

class BrowserTabRoute : Router.Route {
class BrowserTabRouteDecisionHandler : Router.RouteDecisionHandler {
override val name = "browser-tab"

override val result = Router.RouteResult.STOP
override val decision = Router.Decision.CANCEL

override fun matches(
location: String,
Expand Down
51 changes: 26 additions & 25 deletions core/src/main/kotlin/dev/hotwire/core/navigation/routing/Router.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,35 @@ package dev.hotwire.core.navigation.routing
import dev.hotwire.core.lib.logging.logEvent
import dev.hotwire.core.navigation.activities.HotwireActivity
import dev.hotwire.core.navigation.navigator.NavigatorConfiguration
import dev.hotwire.core.navigation.routing.Router.Route
import dev.hotwire.core.navigation.routing.Router.RouteDecisionHandler

/**
* Routes location urls within in-app navigation or with custom behaviors
* provided in [Route] instances.
* provided in [RouteDecisionHandler] instances.
*/
class Router(private val routes: List<Route>) {
class Router(private val decisionHandlers: List<RouteDecisionHandler>) {

/**
* An interface to implement to provide custom route behaviors in your app.
* An interface to implement to provide custom route decision handling
* behaviors in your app.
*/
interface Route {
interface RouteDecisionHandler {
/**
* The route name used in debug logging.
* The decision handler name used in debug logging.
*/
val name: String

/**
* To permit in-app navigation when the location matches this route,
* return [RouteResult.NAVIGATE]. To prevent in-app navigation return
* [RouteResult.STOP].
* To permit in-app navigation when the location matches this decision
* handler, return [Decision.NAVIGATE]. To prevent in-app navigation
* return [Decision.CANCEL].
*/
val result: RouteResult
val decision: Decision

/**
* Determines whether the location matches this route. Use your own custom
* rules based on the location's domain, protocol, path, or any other
* factors.
* Determines whether the location matches this decision handler. Use
* your own custom rules based on the location's domain, protocol,
* path, or any other factors.
*/
fun matches(
location: String,
Expand All @@ -48,7 +49,7 @@ class Router(private val routes: List<Route>) {
)
}

enum class RouteResult {
enum class Decision {
/**
* Permit in-app navigation with your app's domain urls.
*/
Expand All @@ -57,27 +58,27 @@ class Router(private val routes: List<Route>) {
/**
* Prevent in-app navigation. Always use this for external domain urls.
*/
STOP
CANCEL
}

internal fun route(
internal fun decideRoute(
location: String,
configuration: NavigatorConfiguration,
activity: HotwireActivity
): RouteResult {
routes.forEach { route ->
if (route.matches(location, configuration)) {
logEvent("routeMatch", listOf(
"route" to route.name,
): Decision {
decisionHandlers.forEach { handler ->
if (handler.matches(location, configuration)) {
logEvent("handlerMatch", listOf(
"handler" to handler.name,
"location" to location
))

route.handle(location, configuration, activity)
return route.result
handler.handle(location, configuration, activity)
return handler.decision
}
}

logEvent("noRouteForLocation", location)
return RouteResult.STOP
logEvent("noHandlerForLocation", location)
return Decision.CANCEL
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -115,12 +115,12 @@ interface HotwireNavDestination {

/**
* Determines whether the new location should be routed within in-app navigation from the
* current destination. By default, the registered [Router.Route] instances are used to
* current destination. By default, the registered [Router.RouteDecisionHandler] instances are used to
* determine routing logic. You can override the global behavior for a specific destination,
* but it's recommend to use dedicated [Router.Route] instances for routing logic.
* but it's recommend to use dedicated [Router.RouteDecisionHandler] instances for routing logic.
*/
fun route(newLocation: String): Router.RouteResult {
return Hotwire.router.route(
fun decideRoute(newLocation: String): Router.Decision {
return Hotwire.router.decideRoute(
location = newLocation,
configuration = navigator.configuration,
activity = fragment.requireActivity() as HotwireActivity
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner

@RunWith(RobolectricTestRunner::class)
class AppNavigationRouteTest {
private val route = AppNavigationRoute()
class AppNavigationRouteDecisionHandlerTest {
private val route = AppNavigationRouteDecisionHandler()
private val config = NavigatorConfiguration(
name = "test",
startLocation = "https://my.app.com",
Expand All @@ -17,7 +17,7 @@ class AppNavigationRouteTest {

@Test
fun `matching result navigates`() {
assertEquals(Router.RouteResult.NAVIGATE, route.result)
assertEquals(Router.Decision.NAVIGATE, route.decision)
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner

@RunWith(RobolectricTestRunner::class)
class BrowserTabRouteTest {
private val route = BrowserTabRoute()
class BrowserRouteDecisionHandlerTest {
private val route = BrowserRouteDecisionHandler()
private val config = NavigatorConfiguration(
name = "test",
startLocation = "https://my.app.com",
Expand All @@ -17,7 +17,7 @@ class BrowserTabRouteTest {

@Test
fun `matching result stops navigation`() {
assertEquals(Router.RouteResult.STOP, route.result)
assertEquals(Router.Decision.CANCEL, route.decision)
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner

@RunWith(RobolectricTestRunner::class)
class BrowserRouteTest {
private val route = BrowserRoute()
class BrowserTabRouteDecisionHandlerTest {
private val route = BrowserTabRouteDecisionHandler()
private val config = NavigatorConfiguration(
name = "test",
startLocation = "https://my.app.com",
Expand All @@ -17,7 +17,7 @@ class BrowserRouteTest {

@Test
fun `matching result stops navigation`() {
assertEquals(Router.RouteResult.STOP, route.result)
assertEquals(Router.Decision.CANCEL, route.decision)
}

@Test
Expand Down
12 changes: 6 additions & 6 deletions demo/src/main/kotlin/dev/hotwire/demo/DemoApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import dev.hotwire.core.BuildConfig
import dev.hotwire.core.bridge.BridgeComponentFactory
import dev.hotwire.core.bridge.KotlinXJsonConverter
import dev.hotwire.core.config.Hotwire
import dev.hotwire.core.navigation.routing.AppNavigationRoute
import dev.hotwire.core.navigation.routing.BrowserTabRoute
import dev.hotwire.core.navigation.routing.AppNavigationRouteDecisionHandler
import dev.hotwire.core.navigation.routing.BrowserTabRouteDecisionHandler
import dev.hotwire.core.turbo.config.PathConfiguration
import dev.hotwire.demo.bridge.FormComponent
import dev.hotwire.demo.bridge.MenuComponent
Expand Down Expand Up @@ -59,10 +59,10 @@ class DemoApplication : Application() {
BridgeComponentFactory("overflow-menu", ::OverflowMenuComponent)
))

// Register routes
Hotwire.registerRoutes(listOf(
AppNavigationRoute(),
BrowserTabRoute()
// Register route decision handlers
Hotwire.registerRouteDecisionHandlers(listOf(
AppNavigationRouteDecisionHandler(),
BrowserTabRouteDecisionHandler()
))

// Set configuration options
Expand Down

0 comments on commit 0fca5c2

Please sign in to comment.