hotwired / hotwire-native-ios

Hotwire Native for iOS
MIT License
70 stars 7 forks source link

Bridge components aren't always registered #35

Open joemasilotti opened 1 month ago

joemasilotti commented 1 month ago

After debugging a few new Hotwire Native apps I've noticed a common issue. Hotwire.registerBridgeComponents() must be called before a Navigator instance is created. Otherwise, the bridge components never register for the web view.

The quick fix is to defer creation of the Navigator via a lazy variable, like so in the getting started guide:

import HotwireNative
import UIKit

let rootURL = URL(string: "https://hotwire-native-demo.dev")!

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    var window: UIWindow?

-   private let navigator = Navigator()
+   private lazy var navigator = Navigator()

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
+       Hotwire.registerBridgeComponents([...])
        window?.rootViewController = navigator.rootViewController
        navigator.route(rootURL)
    }
}

However, it isn't clear why this has to occur and isn't documented anywhere.

I don't think we can ever get registerBridgeComponents() to work after a URL has been routed. The user agent can't be changed after the request is made and that is what signifies which bridge components are registered. However, having a warning or error if you try to register after a URL has been routed could go a long way in stopping these issues.

I'm also open to other suggestions if folks have ideas!

olivaresf commented 3 weeks ago

After debugging a few new Hotwire Native apps I've noticed a common issue. Hotwire.registerBridgeComponents() must be called before a Navigator instance is created. Otherwise, the bridge components never register for the web view.

What do you think about this https://github.com/hotwired/hotwire-native-ios/pull/41?

In https://github.com/hotwired/hotwire-native-ios/pull/41, all Navigators will subscribe to a .didRegisterBridgeComponents notification that registerBridgeComponents() posts when it's called. Once this notification is received, the navigator will initialize the bridge in both root and modal web views and update its user agent. This will not fix any requests that are in-flight or done so we may need to force a refresh, but it should work in theory.

Could you take that for a spin in one of your projects to see if it works for you?

joemasilotti commented 3 weeks ago

This works great! I swapped out lazy var for a let in the demo app's Navigator and added a bridge component to the index page - worked flawlessly. I added one commit to the PR cleaning up some whitespace.

In-flight requests, as expected, do not correctly register their bridge components. You can replicate with the code below. But I'm OK with not addressing that - what do you think?

import HotwireNative
import UIKit

let rootURL = URL(string: "http://localhost:45678")!

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    var window: UIWindow?

    private let navigator = Navigator()

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        window?.rootViewController = navigator.rootViewController
        navigator.route(rootURL)
        Hotwire.registerBridgeComponents([FormComponent.self])
    }
}

final class FormComponent: BridgeComponent {
    override class var name: String { "form" }

    // ...
}