Directing Turbo Native apps from the server
Tags: turbo, ios, android, rails
Published: 2022-12-01 12:42:29

_This post was extracted and adapted from [The Rails and Hotwire Codex](https://railsandhotwirecodex.com). It also assumes some familiarity with Turbo Native._


When developing for the web, we can send the user to any location during a web request using an HTTP redirect (`3xx` status codes). Turbo Native effectively wraps the website within native navigation. This means a redirect may not always do the trick.

Let's take an example. A **Login** screen is presented modally in the native apps. After a successful login, we want to redirect the user to the home page. In the app, we'd want to dismiss the modal to reveal the screen under it allowing the user to continue with whatever they were doing. A conventional redirect won't work, we need to tell the app to just dismiss the modal.

## Server driven native navigation

The `turbo-rails` gem draws three routes which instruct the native app to do something. These routes don't return any meaningful content; the app is meant to intercept _visit proposals_ to these routes and implement logic to execute the instruction. The routes, and what they instruct, are:

| Route        | Instruction |
|--------------|-----------|
| `/recede_historical_location` | The app should go back |
| `/resume_historical_location` | The app should do nothing in terms of navigation |
| `/refresh_historical_location` | The app should refresh the current page |

Check out the source code to see the [routes](https://github.com/hotwired/turbo-rails/blob/main/config/routes.rb) and the [actions they point to](https://github.com/hotwired/turbo-rails/blob/main/app/controllers/turbo/native/navigation_controller.rb).

These routes are exclusively for the native apps and have no meaning on web. To help with this, `turbo-rails` has methods to conditionally redirect a user if they're using the app. For example, instead of redirecting as below:

```ruby
redirect_to root_path, status: :see_other
```

We would use:

```ruby
recede_or_redirect_to root_path, status: :see_other
```

This will redirect to `/` on the web, and to `/recede_historical_location` in the native apps. Check out all the available redirect methods [in the source code](https://github.com/hotwired/turbo-rails/blob/main/app/controllers/turbo/native/navigation.rb).

Using these methods, the app can be directed to dismiss the login modal after logging in while still redirecting to the root path on web.

Next, redirects to the aforementioned paths need to be intercepted and handled in the apps. We'll call these paths: _path directives_, since they direct the app to do something.

## Intercept and handle path directives

Let's look at iOS. The following delegate method is triggered for every web request from the app.

```swift
extension RoutingController: SessionDelegate {
  func session(_ session: Session, didProposeVisit proposal: VisitProposal) {
    // ...
  }
}
```

In here, we can check if a visit is a _path directive_ and act accordingly.

```swift
extension RoutingController: SessionDelegate {
  func session(_ session: Session, didProposeVisit proposal: VisitProposal) {
    if proposal.isPathDirective {
      executePathDirective(proposal)
    } else {
      visit(proposal)
    }
  }

  func executePathDirective(_ proposal: VisitProposal) {
    guard proposal.isPathDirective else { return }

    switch proposal.url.path {
    case "/recede_historical_location":
      dismissOrPopViewController()
    case "/refresh_historical_location":
      refreshWebView()
    default:
      ()
    }
  }

  private func dismissOrPopViewController() {
    // ...
  }

  private func refreshWebView() {
   // ...
  }
}

extension VisitProposal {
  var isPathDirective: Bool {
    return url.path.contains("_historical_location")
  }
}
```

That'll do the trick on iOS. 

Building a Turbo Native navigation system is non-trivial, so I've had to omit a lot of surrounding code to keep this post on point. [My book](https://railsandhotwirecodex.com) fills in all the gaps if you're interested in digging deeper.

Next, let's look at Android. Custom navigation is handled by creating an `interface` which inherits from `TurboNavDestination`. In this interface, the `shouldNavigateTo(newLocation: String)` method is called for every web request. We can handle _path directives_ in here.

```kotlin
interface NavDestination: TurboNavDestination {

  override fun shouldNavigateTo(newLocation: String): Boolean {
    return when {
      isPathDirective(newLocation) -> {
        executePathDirective(newLocation)
        false
      }
      else -> true
    }
  }

  private fun executePathDirective(url: String) {
    val url = URL(url)
    when (url.path) {
      "/recede_historical_location" -> navigateUp()
      "/refresh_historical_location" -> refresh()
    }
  }

  private fun isPathDirective(url: String): Boolean {
    return url.contains("_historical_location")
  }

  // ...
}
```

## Conclusion

That's how Turbo Native powered apps can be controller from the server! This gives us an immense amount of flexibility and extensibility without ever deploying app updates.

_If you liked this post, check out my book, [The Rails and Hotwire Codex](https://railsandhotwirecodex.com), to level-up your Rails and Hotwire skills! It'll teach you how to build a Turbo Native navigation system and fills in the gaps in this blog post._