Skip to main content

Subscribing to web messages

Web apps presented through a Portal can publish messages to native mobile code using the publish/subscribe interface (pub/sub) available with Portals.

In this step, you will subscribe to messages sent from the web to dismiss the current view.

Exploring the problem

After you navigate to one of of the features in the Jobsync app, you'll notice a peculiar problem: there is no backwards navigation. In fact, this is by design as indicated by Portals/WebAppView.swift:

Portals/WebAppView.swift

_23
import SwiftUI
_23
import IonicPortals
_23
.navigationBarBackButtonHidden()

Web apps consolidated into the Jobsync superapp may consist of multiple views, and therefore need to manage navigation within their scope. Each web app designed for this training contain a header with a back button and when the web app has exhausted its navigation stack, it uses the Portals web library to communicate with iOS code by sending a message to the navigate:back topic.

In the section below, you will use the Portals iOS library to subscribe to the navigate:back topic and dismiss the view when a message is received.

Creating a subscriber

The pub/sub mechanism included in the Portals iOS library relies on two parts that work together: PortalsPubSub and PortalsPlugin. PortalsPlugin is a Capacitor plugin (you will learn about those in the next step) added to a Portal by default that allows web apps to send messages. PortalsPubSub is the class that manages an internal message bus to subscribe to.

info

Pub/sub is bi-directional; messages can be sent from native mobile code to web code as well.

Modify Portals/WebAppView.swift to create a subscriber for the navigate:back topic:

Portals/WebAppView.swift

_26
import SwiftUI
_26
import IonicPortals
_26
_26
struct WebAppView: View {
_26
@EnvironmentObject var credentialsManager: CredentialsManager
_26
@Environment(\.dismiss) var dismiss
_26
let metadata: WebAppMetadata
_26
_26
var body: some View {
_26
PortalView(portal: .init(
_26
name: "debug",
_26
startDir: "portals/debug",
_26
initialContext: credentialsManager.credentials!.toJSObject()
_26
))
_26
.ignoresSafeArea()
_26
.navigationBarBackButtonHidden()
_26
.task {
_26
_26
}
_26
}
_26
}
_26
_26
#Preview {
_26
WebAppView(metadata: WebApps.metadata[0])
_26
.environmentObject(CredentialsManager.preview)
_26
}

Apply the .task modifier to PortalView to execute code when the view becomes active.

Portals/WebAppView.swift

_29
import SwiftUI
_29
import IonicPortals
_29
_29
struct WebAppView: View {
_29
@EnvironmentObject var credentialsManager: CredentialsManager
_29
@Environment(\.dismiss) var dismiss
_29
let metadata: WebAppMetadata
_29
_29
var body: some View {
_29
PortalView(portal: .init(
_29
name: "debug",
_29
startDir: "portals/debug",
_29
initialContext: credentialsManager.credentials!.toJSObject()
_29
))
_29
.ignoresSafeArea()
_29
.navigationBarBackButtonHidden()
_29
.task {
_29
let stream = PortalsPubSub.subscribe(to: "navigate:back")
_29
for await _ in stream {
_29
_29
}
_29
}
_29
}
_29
}
_29
_29
#Preview {
_29
WebAppView(metadata: WebApps.metadata[0])
_29
.environmentObject(CredentialsManager.preview)
_29
}

Within the .task closure, subscribe to the navigate:back topic.

Each entry in the stream is a message sent to the topic.

Portals/WebAppView.swift

_29
import SwiftUI
_29
import IonicPortals
_29
_29
struct WebAppView: View {
_29
@EnvironmentObject var credentialsManager: CredentialsManager
_29
@Environment(\.dismiss) var dismiss
_29
let metadata: WebAppMetadata
_29
_29
var body: some View {
_29
PortalView(portal: .init(
_29
name: "debug",
_29
startDir: "portals/debug",
_29
initialContext: credentialsManager.credentials!.toJSObject()
_29
))
_29
.ignoresSafeArea()
_29
.navigationBarBackButtonHidden()
_29
.task {
_29
let stream = PortalsPubSub.subscribe(to: "navigate:back")
_29
for await _ in stream {
_29
self.dismiss()
_29
}
_29
}
_29
}
_29
}
_29
_29
#Preview {
_29
WebAppView(metadata: WebApps.metadata[0])
_29
.environmentObject(CredentialsManager.preview)
_29
}

Handle the message by calling dismiss(), the @Environment method that will pop the current view from navigation.

Apply the .task modifier to PortalView to execute code when the view becomes active.

Within the .task closure, subscribe to the navigate:back topic.

Each entry in the stream is a message sent to the topic.

Handle the message by calling dismiss(), the @Environment method that will pop the current view from navigation.

Portals/WebAppView.swift

_26
import SwiftUI
_26
import IonicPortals
_26
_26
struct WebAppView: View {
_26
@EnvironmentObject var credentialsManager: CredentialsManager
_26
@Environment(\.dismiss) var dismiss
_26
let metadata: WebAppMetadata
_26
_26
var body: some View {
_26
PortalView(portal: .init(
_26
name: "debug",
_26
startDir: "portals/debug",
_26
initialContext: credentialsManager.credentials!.toJSObject()
_26
))
_26
.ignoresSafeArea()
_26
.navigationBarBackButtonHidden()
_26
.task {
_26
_26
}
_26
}
_26
}
_26
_26
#Preview {
_26
WebAppView(metadata: WebApps.metadata[0])
_26
.environmentObject(CredentialsManager.preview)
_26
}

Once the view has navigated away, the subscriber is terminated along with PortalView.

info

Additional ways to create subscribers, including UIKit examples, can be found at this link.

Testing the subscription

Build and run the Jobsync app and navigate to one of the features in the dashboard view. Switch from the 'Initial Context' tab to the 'Publish/Subscribe' tab.

Here, you can test Portal's pub/sub mechanism by entering navigate:back into the 'Topic:' input field under the 'Publish' heading. Press the 'Publish' button, and the view should dismiss, navigating back to the dashboard view.

What's next

The pub/sub mechanism available within the Portals library is ideal for simple use cases, such as allowing a web app presented through a Portal to request native navigation. However, it is not suitable for more complex use cases. In the next step of this module, you will learn about Capacitor plugins, which also communicate bi-directionally, but in a more structured manner suitable for complex use cases.