Talks
Strada: Bridging The Web and Native Worlds
Summarized using AI

Strada: Bridging The Web and Native Worlds

by Jay Ohms

The video titled 'Strada: Bridging the web and native worlds' features Jay Ohms from 37signals discussing the launch of Strada, a framework designed to enhance Turbo Native apps by bridging the communication gap between web and native components. The presentation addresses the limitations of traditional Turbo Native setups, particularly the disconnect between WebView content and native app functionalities.

Key points discussed include:

  • The Challenge: Apple and Google provide robust native app APIs, but existing Turbo Native apps struggle to communicate effectively between the web content and native features.
  • The Solution: Strada establishes a method for native and web applications to exchange information through a JSON-based component system, eliminating the need for app-specific JavaScript and allowing for dynamic state updates.
  • Component Design: Strada components allow the web app to dictate the UI of the native app. This includes functionalities like moving buttons for better user experiences based on context (e.g., moving a save button to avoid issues with the virtual keyboard).
  • Progressive Enhancement: Developers can start with a fully web-driven UI and incorporate native features as needed, ensuring backward and forward compatibility between web and native versions of the app.
  • Example Implementations: Jay provides examples of using Strada to create seamless interactions, such as linking web form submissions to native behaviors, utilizing shared event messaging for user actions, and transforming web flash messages into native alerts.
  • Future Work: Ohms elaborates on ongoing enhancements to Strada, including additional components that seamlessly blend web and native functionalities to improve overall app performance and user experience.

In conclusion, Strada represents an innovative step in hybrid app development, connecting existing web applications with native app capabilities more effectively, enabling faster updates, and enhancing user experience through integrated features.

00:00:15.240 Hey everyone, I am Jay Ohms. I work at 37signals and lead the mobile team.
00:00:21.720 Before we get into Strada, there's an important question I know some of you are asking, so let's just get it out of the way right now: Did you seriously tease Strada and Hotwire's launch years ago to get my hopes up, only to take a really long time to finally launch? I've had to wonder what it is, how it works, and if I should care for a long time now.
00:00:36.600 Yes, and now that we have that out of the way, I'd love to tell you more about it. I'll start with a high-level overview before we dive into the details.
00:01:07.439 As you all know, the web is really great at many things. It’s broadly accessible, allows fast development, and is excellent at displaying server-driven UI. Changes can be deployed quickly and easily. However, if any of you are mobile developers, you are likely familiar with the long and slow review processes, which can be a painful part of the job.
00:01:25.119 Native apps are optimized for small screens, providing high-fidelity experiences. They also allow developers to take advantage of platform APIs and conventions. Apple and Google have done a great job providing excellent platform APIs that we can use, and you also get to use UI components for free.
00:01:36.600 At 37signals, our apps are hybrid, built on Turbo Native libraries. We have two types of screens: native screens that render UI from JSON endpoints and web view screens driven by Turbo, which display web content within a rectangle on the screen.
00:02:00.240 While we can decorate the web view with some native chrome around it, there exists a problem: there is a disconnect between the web view on the screen and the native shell features of your application. There is no easy way for the native app to know what is happening in the web view.
00:02:21.599 For a long time, we built our apps on Turbo libraries—primarily Basecamp and Hay—and recognized the need for a way to communicate between the web view and the native app, and vice versa. We went through several approaches and mistakes along the way.
00:02:34.280 One issue we encountered was packaging app-specific JavaScript within our native apps to manipulate and query the web view DOM. This was a very fragile approach that would break whenever we updated the web app.
00:02:54.079 This became a major problem since the only way to fix the bug was to ship a new app update and wait through the usual review times. Native apps also do not know when there are state changes in the web view DOM.
00:03:06.040 At 37signals, we have a web team consisting of web programmers and designers, and a mobile team with mobile programmers and designers. While we work together, we also function independently. It was tricky for the web designers and programmers to know when changes to the web app could negatively impact the native apps.
00:03:16.679 We realized we needed a long-term solution to this issue. We had a method in Basecamp that worked to some extent but was not ideal. Therefore, we set out to create a framework that could solve these problems.
00:03:42.280 We aimed to establish a bridge between our web code and our native app code, making it a component-based framework. This would allow us to scale alongside both the web app and the native apps effectively.
00:04:01.679 We formulated some rules to guide our solution. One was that the apps must not rely on documentation markup present on the page. The knowledge that the native app holds should be entirely divorced from the actual DOM in the web view.
00:04:22.140 The native app cannot package any app-specific JavaScript, meaning our communication between native and web apps would occur using JSON. The web app should be able to send state changes from the web view to the native app to inform it.
00:04:50.160 The native app should also be capable of responding to state changes back to the web. Another important aspect is that if you possess a Turbo Native app, you might want to deploy a new feature quickly on the web.
00:05:10.040 However, it could take a week to push your app update. During this transition, we need a way to bridge old clients to new clients, and Strada uses an opt-in component system where behaviors change based on what is supported by the native apps.
00:05:28.760 We also aimed to bridge web UI to native UI cleverly. We already had some interactions developed on the Basecamp side, and were eager to build more sophisticated native features driven by the web app.
00:05:48.760 After spending considerable time, we developed Strada, which has been implemented in Hey since its launch in 2020. It consists of libraries across all platforms, enabling native control in hybrid mobile apps driven by the web.
00:06:06.320 What do I mean by driven by the web? It means your web app determines when and where native controls show up in your native applications. The native apps don't decide; instead, your web app controls that.
00:06:32.120 Moreover, Strada leverages Stimulus directly, giving you the same power in your web components as you are familiar with using Stimulus.
00:06:56.760 Let's look at an example. Here’s a screen in Hey, which is a screen to edit your profile. If you notice at the bottom, there’s a save button shown in the web view.
00:07:14.440 In mobile apps, it’s quite common to have your submit buttons, save buttons, or next buttons in the top-right toolbar area. This setup is convenient for several reasons.
00:07:33.680 For example, when the virtual keyboard is open, you can no longer see the save changes button. The user would then have to scroll to access it, creating a less than ideal user experience.
00:07:52.560 To address this, we can move the submit button to the top-right of the app bar and hide the submit button in the web view. The native app will notify the web app when the native submit button is tapped.
00:08:11.240 The web submit button will process as if the user tapped the web view button directly. The goal is to hook up this submit functionality to Strada.
00:08:30.440 Let’s start by adding a data-controller attribute similar to how it's done in Stimulus. This ties to a bridge form component, and we will also designate the submit button as a target for the component.
00:08:47.440 To create a web component for Strada, extend the bridge component class which itself is an extension of a Stimulus controller. You will inherit all the features of a Stimulus controller while additionally receiving Strada-specific functionality.
00:09:04.440 Importantly, you need to name your component. This is a plain text identifier that both the web and native apps will agree on, used for communication between the two.
00:09:23.560 When the submit button is connected on the page, we'll wrap the submit button target in a bridge element, allowing it to inherit Strada-specific behaviors.
00:09:46.560 You can set up titles for your elements easily with Strada. For example, if the web app button says 'Save Changes', and you wanted it just to say 'Save', that would be straightforward to implement.
00:10:03.240 Now, let’s send a message to the native app. Each message will contain an event name, which your web component and native component will agree upon for compatibility.
00:10:22.760 The data sent will be in JSON format, including the submit title to the native app. Additionally, a callback is provided that will be triggered upon receiving a response from the native component.
00:10:41.840 When the native component replies, it will trigger the submit button on the form. This maintains the existing workings of the form without changing validation or submission process.
00:10:59.440 Let’s explore the bridge message sent if your native app supports the form component. It includes an ID, a name indicating it's a form component, the event name, and the data including the submit title.
00:11:14.560 On the native side, we will extend the bridge component class, which is in Kotlin. Here, we will implement the onReceive function to handle incoming messages.
00:11:31.360 When we receive the connect message event, we will process the incoming data, deserialize the JSON, and create the native toolbar button.
00:11:47.920 While I won’t display the entire code here for brevity, you can find the complete code in our Turbo Native demo apps. The crucial step is placing the button in the toolbar and setting the text based on the incoming data.
00:12:06.560 Once the user taps the button, we will reply back to the last received connect event message. While optional, we can pass additional data during the reply, but it’s not necessary in this instance.
00:12:23.360 Connecting the dots requires some initial setup on the native side. You will need to initialize the bridge with the web view instance, update the web view user agent, and delegate Turbo lifecycle events.
00:12:43.520 When the native app and web app declare the components they support, it will influence how these components communicate effectively.
00:13:02.279 Strada automatically updates the document element in your web app to reflect the platform and supported components, ensuring your backend is informed.
00:13:24.599 Returning to the example, the button in the top-right corner will trigger a reply to the message, enabling the web to trigger the submit button and submit the form as expected.
00:13:37.919 There is an easy way to address the intuitive problem of having both buttons visible. By employing the registered bridge component, we can utilize scoped CSS to hide the submit button in the web view once the native component is supported.
00:13:54.719 The mechanism allows a seamless experience where any submit buttons inside of a Bridge Form controller are hidden whenever the form component is supported in the native app.
00:14:14.960 This setup maintains both backward and forward compatibility. If a new feature is launched with a web form and the native app hasn’t yet been updated to support the form component, the web button remains visible.
00:14:29.520 Conversely, once the native app updates to support the component, it shows the native button instead.
00:14:52.159 What you see is that the flow of events is straightforward. When a bridge component activates on the page, the event is communicated through the bridge and reaches your bridge component.
00:15:16.079 When the native side replies to the original message, the process reverses through the bridge back to the web bridge component. This creates a nice example of progressive enhancement.
00:15:34.520 You can begin with a fully web-driven experience in your web view, layering native features as needed. The beauty here is that your web component and native component are cleanly separated from your app features.
00:15:50.320 Thus, for any form in your web app, you simply add your bridge form controller attribute along with the submit target, and it works seamlessly across the entire app.
00:16:10.360 Let’s examine a more advanced example. This is a screen in Hay where a menu is fully driven by the web. The purple menu here is unaware of the features it displays.
00:16:26.040 It simply displays a list of items driven by the web, passing along icon URLs generated from the web as well.
00:16:46.420 To build this menu as a Strada component, we begin with a simple `div` and apply a data controller attribute, which initializes both a menu controller for web functionality and a bridge menu controller for the bridge component.
00:17:06.280 Whenever a button in the web menu is clicked, it should call the appropriate show function on the bridge component, giving it a chance to display if the native app supports it.
00:17:24.520 By setting a title for the menu, the title can be passed easily along with any other data required when menu items are clicked.
00:17:41.160 The process involves sending a display event message to the native app with the title and items data. We also implement a callback function for when a menu item is selected.
00:17:53.920 Following this, we extract the index of the selected item from the reply message which the native component returns, and then we trigger the click event on the corresponding button.
00:18:08.920 Now, let’s shift focus to building a native component. On the iOS side, we extend the bridge component class, implementing the onReceive function to handle incoming messages.
00:18:30.520 This will allow us to process the display event message received from the web and trigger the appropriate UI, such as showing an alert sheet.
00:18:50.520 While the complete code isn't presented here, the demo app contains code examples demonstrating the implementation of alert sheets and their interactions.
00:19:09.760 Once a menu item is tapped, the onItemSelected function is called, and we reply back to the display event message with the selected index of the item.
00:19:27.520 This message returns to the web component, which will then utilize the index to simulate a button click on the corresponding item.
00:19:42.720 Throughout the Hay app, we've implemented various components to enhance functionality. For instance, we’ve built a floating action bar for emails that can show menus.
00:20:00.240 We also enable users to highlight text and create clips of content displayed through native UI, seamlessly integrating web and native experiences.
00:20:16.560 There’s a flash message component that transforms web flash messages into native UI, taking advantage of platform conventions.
00:20:31.760 Additionally, we have a nav button component that lets us show small icons in the app bar and an overflow menu for less common actions.
00:20:48.640 The page component connects on every single page within the web app, providing features such as styled page titles, and animating title transitions during scrolling.
00:21:04.920 Our print component also addresses limitations in web view APIs by allowing users to print emails driven directly by the web app.
00:21:26.680 Finally, we’ve developed a tricks component allowing for the establishment of toolbars with native UI, showcasing our commitment to building a robust library.
00:21:44.840 We continue to add more examples and enhance the library, and that concludes my presentation. Thank you for your attention.
00:22:26.240 Thank you very much.
Explore all talks recorded at Rails World 2023
+20