Get Started with Strada! A First Look on iOS

Tada! I mean, Strada!

If you’ve been following along, you may know I’ve been getting quite interested in Turbo Native iOS for Rails. If you haven’t been following along.. why not!

Anyhow, to recap, Turbo Native enables rapid development of hybrid apps, by wrapping a Rails 7 turbo-enabled webapp in a native iOS or Android shell. It really is fantastic and exposes so much more than just a simple web browser.

The big news yesterday was the launch of the last piece of the puzzle, Strada from 37 Signals. Strada is the long awaited third pillar in the Hotwire trifecta, along with the previously released Turbo and Stimulus.

Interestingly, Strada doesn’t actually provide any new capabilities we couldn’t already bring to Turbo Native apps, however, in true 37signals style it lays out a “standard” way to abstract all the mess into nicely formed components. It bringing some order and structure to “how” to do it. And I love it already!

What we’re going to build

We’re going to copy some of the example code from the demo, and make it work in a Rails app rather than the Express demo. In my case I updated my existing nav menu, and enhanced the Native App with an UIActionSheet from the iOS demo.

I’m assuming you have a Rails app and Turbo Native app already and are interested to enhance it with Strada. If not, read along but you will need to spin up a new app to work with on your own time.

Here’s a side by side of web vs native so you can see what we’re building:

Mobile Web vs Native iOS

The real magic here, and it really is magic, is that we’re just using the existing web markup shown on the left, and are able to level up and introduce real native iOS controls as and when we feel like it to give our apps a native platform boost. Even better this means we can add or edit our menu in our Native app based on the web markup, so without needing to resubmit our apps to Google or Apple in many cases!

Getting Started - A First Look

Of course I wanted to jump right in and try and add some Strada magic to the top secret new app I’m working on (more on that soon) and here’s what I learned so far.

The documentation is already fantastic along with demo code and apps, and I recommend you start there. What I did find though is the demo app is not Rails, and I wanted to get this going with my lean Rails app using import maps and all the defaults.

Adding Strada to your Rails App

After a lot of trial and error, and getting a bit lost trying to initialise Strada and the demo components based on the demo app comically I found Strada “Just WorksTM if you have Stimulus set up already, since it extends Stimulus.

Assuming you have an existing app like me, and are using Stimulus, this guide is for you! If not, you can learn about how to get started here.

I couldn’t get bin/importmap pin to find Strada yet.. it had only been released a few hours so no surprise, but we can add it manually. So first, open up config/importmap.rb and add the following:

pin “@hotwired/strada”, to: “https://ga.jspm.io/npm:@hotwired/strada@1.0.0-beta1/dist/strada.js”, preload: true

[UPDATE]: Since I wrote this article last month, Strada is now ready to install directly, so run this in your terminal to get it added:

bin/importmap pin @hotwired/strada

That’s it!! I messed around for a couple of hours trying to import the components manually based on the demo app setup, but in the end, it just worked automatically once @hotwired/strada was installed thanks to the updated eager loading of stimulus controllers. Great!

Copy the demo components

Since we’re just trying this out, and want to try a menu, we’ll reuse the demo components as-is.

Create a new directory in your app alongside any stimulus controllers you may have already here app/javascript/controllers/bridge/ and copy in the three bridge files form_controller.js, menu_controller.js, and overflow_menu_controller.js from the demo you can find here: https://github.com/hotwired/turbo-native-demo/tree/main/public/javascript/controllers/bridge

We’ll also need the stimulus controller for out menu menu_controller.js so grab that from https://github.com/hotwired/turbo-native-demo/tree/main/public/javascript/controllers/ and copy the file into app/javascript/controllers/ in your app.

Add the Web Menu

Next we need to connect our menu to the stimulus controllers. This requires standard Stimulus data-* attributes that should be straightforward. Again we can start with the markup from the demo. Here’s what I ended up with for this example:

<div data-controller="menu bridge--menu">
  <button
    class="button"
    data-controller="bridge--overflow-menu"
    data-action="click->bridge--menu#show click->menu#show"
    data-bridge-title="Options">
    Menu
  </button>
  <div
    class="dialog"
    data-menu-target="dialog"
    data-action="click@window->menu#hideOnClickOutside">
    <div class="dialog-content">
      <span class="dialog-close" data-action="click->menu#hide">&times;</span>
      <p data-bridge--menu-target="title">Select an option</p>
      <button
        class="button"
        data-menu-target="item"
        data-bridge--menu-target="item"
        data-action="click->menu#itemSelected">
        Edit Profile
      </button>
      <button
        class="button"
        data-menu-target="item"
        data-bridge--menu-target="item"
        data-action="click->menu#itemSelected">
        Sign Out
      </button>
    </div>
  </div>
</div>

Strada iOS

Now we’re done with the web, we need to add our matching components in the Turbo Native app. Again we can just drop in the code from the turbo-ios demo for the purposes of this experiment, so copy these 4 files from https://github.com/hotwired/turbo-ios/tree/main/Demo/Strada into your Turbo Native code base. In my case I created a Strada group in XCode but they can go where you like.

BridgeComponent+App.swift
FormComponent.swift
MenuComponent.swift
OverflowMenuComponent.swift

The other file we need to copy over from the demo into our code base is TurboWebViewController.swift so grab that from https://github.com/hotwired/turbo-ios/blob/main/Demo/TurboWebViewController.swift and drop it in your app.

Finally, assuming you’re using SceneDelegate we need to include the components we’re using in the userAgent and also initialise the Bridge.

Wherever you’re setting up your WebView, change the code where you set the user agent, in my case I replaced the configuration.applicationNameForUserAgent = TurboConfig.shared.userAgent assignment as shown in the diff below. Also note, we need to initialise the Strada Bridge as also shown here:

-            configuration.applicationNameForUserAgent = TurboConfig.shared.userAgent
+            let stradaSubstring = Strada.userAgentSubstring(for: BridgeComponent.allTypes)
+            let userAgent = "\(TurboConfig.shared.userAgent) \(stradaSubstring)"
+            configuration.applicationNameForUserAgent = userAgent

             let webView = WKWebView(frame: .zero, configuration: configuration)

+            // Initialize Strada bridge.
+            Bridge.initialize(webView)

Cleanup

If like me you were previously handling this javascript to native bridge manually, you can delete all that crufty code. Get rid of all the ScriptMessageDelegate etc. That felt good!

TurboWebViewController

The final thing we need to do for now is to replace the VisitableViewController Turbo Native is using with the new TurboWebViewController we previously added that includes all the hooks to the Bridge in the lifecycle events. In my case, since I’m using the wonderful TurboNavigator from the equally wonderful Joe Masilotti I had to go in to the main class that handles the routing and replace it as shown in this diff

     private func controller(for proposal: VisitProposal) -> UIViewController? {
-        let defaultController = VisitableViewController(url: proposal.url)
+        let defaultController = TurboWebViewController(url: proposal.url)

Trying it out

Here we’re specifying the two Stimulus Controllers menu, and bridge--menu with data-controller="menu bridge--menu" and adding data-actions to the main menu button and each item button in the list.

If you run this now in a browser, you should see the dialog pop up in HTML when you click “menu” but when you open in a Turbo Native app context you should now see the ellipsis menu icon in the iOS navigation bar at the top right, and when you click that, you will see the native UIActionSheet pop up with the same data as the web version.

Next Steps

Obviously this is just extending the demo a little and not very useful, but should get you started. Having got this wired up into my app, I’m super excited to try and implement the UIMenu I am using as a BridgeComponent and add in the icons and links etc.

I also wired up the FormComponent from the demo to the ‘edit profile’ link in the screenshot above, and it worked out of the box! Too much to include here but should be pretty straightforward to try if you look at the turbo demo web app.

This is all rather new, less than 24-hours old, and I wrote most of this code into the early hours of the night last night, so there may be issues, changes, challenges. Do let me know if there are or if I can help!