How to use Strada with Turbo Navigator

How to use Strada with Turbo Navigator

Not too long, ago 37signals?officially launched Strada, the long-awaited third and final piece of Hotwire. It unlocks progressive enhancement?of native components in Turbo Native apps.

But there are a few hoops you need to jump through to use it. This is especially true when working with Turbo Navigator.

Turbo Navigator is a drop-in class for?Turbo Native?apps to handle common navigation flows. It removes 100+ lines of boilerplate and will be upstreamed into turbo-ios soon.

Earlier this week someone asked for help integrating Strada with Turbo Navigator. This made me realize it isn’t as straightforward as my response made it seem!

So here’s a step-by-step guide to integrating Strada with Turbo Navigator.

Getting started

We will start with the demo iOS and Rails app included with Turbo Navigator. Clone the project to your machine from the GitHub repo.

Open Demo/Demo.xcodeproj in Xcode and start the Rails server located at Demo/Server/via bin/dev.

Launch the app in Xcode via ProductRun or with ? + R. You should see the home screen of the Rails server launch in the iOS Simulator.

Turbo Navigator demo app
Turbo Navigator demo app

With everything running we can start integrating Strada, first on the server then in the app.

Integrate Strada with our Rails app

Integrating Strada with our Rails app requires three steps:

  1. Add the JavaScript package
  2. Create a Strada component
  3. Wire up the component

1. Add the JavaScript package

To add the JavaScript package we run the following from the Demo/Server/ directory.

yarn add @hotwired/strada        

Note that if you are using importmap in your Rails app you would instead run:

bin/importmap pin @hotwired/strada        

2. Create a Strada component

Next, create a new Strada component. Use the Stimulus generator to build an empty Stimulus controller nested under the bridge/ subdirectory.

bin/rails generate stimulus bridge/hello        

Update the controller to following, the bare minimum needed. When connected, this component will fire the "connect" message to our iOS app.

// app/javascript/controllers/bridge/hello_controller.js

import { BridgeComponent } from "@hotwired/strada"

export default class extends BridgeComponent {
  static component = "hello"

  connect() {
    super.connect()

    this.send("connect", {}, () => {
    })
  }
}        
A heads up that this code looks a lot better on my website.

3. Wire up the component

Our last step on the server is to add the HTML markup needed to connect the controller. Add the following to the top of the navigations show template. Note the double dashes that namespace to our bridge/ directory.

<!-- app/views/navigations/show.html.erb -->

<div data-controller="bridge--hello">        

Integrate Strada with our iOS app

Integrating Strada with our iOS app requires a bit more work, six steps in total:

  1. Add the Swift package
  2. Create a Strada component
  3. Register the component
  4. Create a Strada-enabled view controller
  5. Configure the web view for Strada
  6. Tell Turbo Navigator to use the new view controller

But there’s good news! If you’ve followed the official Strada Quick Start Guide you can skip all the way to step 5.

1. Add the Swift package

Open the Xcode project and click FileAdd Package Dependencies…

Copy-paste the strada-ios URL in the upper right and click Add Package.

https://github.com/hotwired/strada-ios        
Add the strada-ios package to our iOS app

2. Create a Strada component

Create a new file by right-clicking the Demo group on the left and clicking New File… Select Swift File from the iOS tab and click Next.

New Swift file dialog in Xcode

Name this file HelloComponent and click Create.

Creating a new file in Xcode named HelloComponent

Replace the contents of this file with the following. This registers a native component with the "hello" name to match the Stimulus controller we built in Rails. Any time a message is received it will log to the console.

// HelloComponent.swift

import Strada

class HelloComponent: BridgeComponent {
    override class var name: String { "hello" }

    override func onReceive(message: Message) {
        print(#function, message)
    }
}        

3. Register the component

Create another Swift file and name it BridgeComponent+App. Replace the contents with the following. This holds a global reference to all of our Strada components to refer to later.

// BridgeComponent+App.swift

import Strada

extension BridgeComponent {
    static var allTypes: [BridgeComponent.Type] {
        [
            HelloComponent.self
        ]
    }
}        

4. Create a Strada-enabled view controller

Create a third Swift file and name this one TurboWebViewController. Replace the contents with the following, taken directly from the Quick Start Guide. This controller helps bridge the gap to Strada, passing along view lifecycle events.

// TurboWebViewController.swift

import Strada
import Turbo
import WebKit

final class TurboWebViewController: VisitableViewController, BridgeDestination {
    private lazy var bridgeDelegate = BridgeDelegate(
        location: visitableURL.absoluteString,
        destination: self,
        componentTypes: BridgeComponent.allTypes
    )

    // MARK: View lifecycle

    override func viewDidLoad() {
        super.viewDidLoad()
        bridgeDelegate.onViewDidLoad()
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        bridgeDelegate.onViewWillAppear()
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        bridgeDelegate.onViewDidAppear()
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        bridgeDelegate.onViewWillDisappear()
    }

    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        bridgeDelegate.onViewDidDisappear()
    }

    // MARK: Visitable

    override func visitableDidActivateWebView(_ webView: WKWebView) {
        bridgeDelegate.webViewDidBecomeActive(webView)
    }

    override func visitableDidDeactivateWebView() {
        bridgeDelegate.webViewDidBecomeDeactivated()
    }
}        

5. Configure the web view for Strada

Back in our scene delegate we need to configure Strada to use our components. Create a new private function with the following. Don’t forget to import Strada and WebKit at the top!

// SceneDelegate.swift

import Strada
import WebKit

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    /* ... */

    private func configureStrada() {
        TurboConfig.shared.userAgent += " \(Strada.userAgentSubstring(for: BridgeComponent.allTypes))"

        TurboConfig.shared.makeCustomWebView = { configuration in
            let webView = WKWebView(frame: .zero, configuration: configuration)
            Bridge.initialize(webView)
            return webView
        }
    }
}        

Call this function in scene(_:willConnectTo:options:) right after the guard statement. This ensures Strada is configured before we actually route the URL.

// SceneDelegate.swift

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    /* ... */

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        guard let windowScene = scene as? UIWindowScene else { return }

        configureStrada()  // <-- Add this line.

        self.window = UIWindow(windowScene: windowScene)
        self.window?.makeKeyAndVisible()

        self.window?.rootViewController = self.turboNavigator.rootViewController
        self.turboNavigator.route(baseURL)
    }

    /* ... */
}        

6. Tell Turbo Navigator to use the new view controller

Finally, we to tell Turbo Navigator to use our TurboWebViewController when visiting pages.

Implement handle(proposal:) in the extension at the bottom of our scene delegate. Use the .acceptCustom option to return an instance of our new controller.

// SceneDelegate.swift

extension SceneDelegate: TurboNavigationDelegate {
    func handle(proposal: VisitProposal) -> ProposalResult {
        .acceptCustom(TurboWebViewController(url: proposal.url))
    }
}        

Testing the integration

Run the app in Xcode and click on the Basic navigation link. If all went well you should see the following in the Xcode logs when the page loads.

If you don’t see the debug area then click ViewDebug AreaShow Debug Area.

Xcode printing out the received Strada message

If you’re not seeing this then then try enabling Strada’s debug logging. Add this to the top of the configureStrada() function.

Strada.config.debugLoggingEnabled = true        

What’s next?

Integrating Strada requires multiple steps to get started. And an additional two for Turbo Navigator projects. At least once initially configured adding additional components only requires the component file and referencing it in BridgeComponent+App.

But I feel like that is still too much work. And I’m hoping to change that.

I’m working on something that will make it even easier to integrate Strada into Turbo Native apps. Subscribe to my newsletter to be the first to know when I release something public!

要查看或添加评论,请登录

Joe Masilotti的更多文章

  • Two new chapters and recent podcast appearances

    Two new chapters and recent podcast appearances

    Hey folks! I have a lot of exciting updates, so let’s dive right in. I finished the first draft of my book! I’ve been…

    1 条评论
  • What you’ll learn in “Hotwire Native for Rails Developers”

    What you’ll learn in “Hotwire Native for Rails Developers”

    A lot of folks have been asking me what exactly they’ll learn in my new book, Hotwire Native for Rails Developers. So…

  • Hotwire Native book, now in beta!

    Hotwire Native book, now in beta!

    You can now buy my book, Hotwire Native for Rails Developers! Create intuitive and maintainable mobile apps powered by…

    31 条评论
  • Jumpstart Pro, now with Hotwire Native!

    Jumpstart Pro, now with Hotwire Native!

    Hey folks, I’m excited to share that Chris Oliver and I just launched a major update to Jumpstart Pro - the code…

    2 条评论
  • Burnout today, parental leave tomorrow

    Burnout today, parental leave tomorrow

    Hey there! My second son will be born in a few days. And I’m doing everything I can to spend as much time with him as…

  • The power of Turbo Native path configuration

    The power of Turbo Native path configuration

    Hey there, As you know, Turbo Native helps Rails developers build iOS and Android apps quickly, bridging the gap…

  • I signed a book deal! ??

    I signed a book deal! ??

    Hey folks, I’m thrilled to share some exciting news: I’ve officially signed a book deal with Pragmatic Bookshelf!…

    39 条评论
  • The secret tool to launch your Rails business in the app stores

    The secret tool to launch your Rails business in the app stores

    Let’s talk mobile. In our always-on, app-driven world, having a standout mobile presence isn’t just nice to have; it’s…

  • A roadmap for building Turbo Native apps

    A roadmap for building Turbo Native apps

    Hey folks, A big thank you to everyone that dropped by my live Q&A last week! I answered 20ish questions over the 90…

  • Turbo Native + Rails Q&A session

    Turbo Native + Rails Q&A session

    Hey folks, how’s your week going? I’ve been inside for the past… four days because Portland is covered in a sheet of…

    4 条评论

社区洞察

其他会员也浏览了