Bridge Components

Let’s move forward in transforming the application to make it more native. An effective approach is to use Bridge Components (formerly known as Strada Components), which create an integration between the web application and iOS, enabling the addition of native elements.

First Bridge Component

In the previous chapter, we identified the New URL button on the URLs screen. We’ll replace it with a native button, removing it from the web interface to provide a more integrated and native look.

Step-by-step guide to creating the New URL button in its native version using a Bridge Component:Create the Bridge Component in iOS:

iOS Bridge Button Component

In the iOS project, right-click on the file tree and select New Empty File, or use the shortcut Command + N. Create a Swift file named ButtonComponent. The following code includes comments to assist understanding, especially for those new to Swift.

import HotwireNative
import UIKit

final class ButtonComponent: BridgeComponent {

  // Assigns a name to the component, allowing it to connect
  // to the Stimulus Controller using this string.
  override class var name: String { "button" }

  // During the Stimulus `connect` event, the component
  // receives necessary information, such as the button title.
  override func onReceive(message: Message) {

      // Use print(message) to inspect the data received from Rails.
      // The content will be displayed in the Xcode logs.
      guard let viewController else { return }
      addButton(via: message, to: viewController)
  }

  private var viewController: UIViewController? {
    delegate.destination as? UIViewController    
}

  // Function responsible for setting up and rendering the native button
  // on the iOS app screen, adding it to the right side of the Navbar
  // in response to the `connect` event from the Stimulus Controller.
  private func addButton(via message: Message, to viewController: UIViewController) {

    guard let data: MessageData = message.data() else { return }

    let action = UIAction { [unowned self] _ in
      self.reply(to: "connect")
    }

    let item = UIBarButtonItem(title: data.title, primaryAction: action)
    viewController.navigationItem.rightBarButtonItem = item
  }
}

// Decodes the message received from the Stimulus Controller
// into the `MessageData` struct. Classes, structs, and extensions
// were discussed in the Swift section.
private extension ButtonComponent {
  struct MessageData: Decodable {
    let title: String
  }
}

Register the component in Hotwire Native to make it available in the project and accessible as needed. In the AppDelegate file, add the following code below the PathConfiguration setup:

Hotwire.registerBridgeComponents([
  ButtonComponent.self
])

Connecting Rails to the Native Component

Ensure that the necessary packages are installed in your Rails project:

./bin/importmap pin @hotwired/stimulus @hotwired/hotwire-native-bridge

To complete the integration and enable Rails to call the native component, create a new Stimulus Controller at the specified path and name:

app/javascript/controllers/button_controller.js

Stimulus Controller Implementation in Rails

import { BridgeComponent } from "@hotwired/hotwire-native-bridge"

export default class extends BridgeComponent {
  // Remember the string in the Swift component?
  static component = "button"

  // The `connect` method is executed when the element is ready,
  // informing the bridge about the component being called and
  // defining the native button's action via a callback.
  connect() {
    super.connect()

    const element = this.bridgeElement
    const title = element.bridgeAttribute("title")
    this.send("connect", {title}, () => {
      this.element.click()
    })  
  }
}

Connecting the New URL Button to the Stimulus Controller

To complete the process, the connect method of the Stimulus Controller needs to be triggered. This is achieved through an HTML element. Update the New URL button in the file:

app/views/urls/index.html.erb

In the link_to helper, add the required data attributes to activate the Stimulus Controller, including the controller name and the button title:

<%= link_to "Back to urls", urls_path, class: "ml-2 rounded-lg py-3 px-5 bg-gray-100 inline-block font-medium", 
data: { controller: "button", bridge_title: "New Url" } %>

After the update, run the iOS app again (Command + R). Navigate to URLs and observe that the native button appears as implemented.

When clicked, it performs the same action as the web button. This works because the Stimulus Controller, via the callback in the connect method and the this.send call, ensures that the event triggered by the native button click is equivalent to clicking the web button (this.element.click()).

Screenshot 2024-12-22 at 11.41.15.png

Recap

To understand Bridge Components, it is essential to consider three main elements:

  1. Swift Component: Native code responsible for implementing functionality on iOS (or Android).

  2. Stimulus Controller: JavaScript code that connects to an HTML element, sends messages to the native bridge to execute or display functionalities, transmits the data needed to render the native element, and defines the callback.

  3. HTML Markup (ERB): HTML structure that invokes the Stimulus Controller, passing the necessary data-bridge attributes for the native component to function.

These steps, in a simplified manner, represent the integration flow using the native bridge.

Screenshot 2024-12-23 at 23.20.09.png