How to Communicate with a Portal Web Application
The PortalsPlugin
provides useful features to aid in communication between your Web and Native applications. It is included in the Ionic Portals library by default and takes advantage of the Capacitor Plugin system.
Setup
iOS
Follow the Getting Started Guide to install the Ionic Portals library into your native mobile projects. The PortalsPlugin
is automatically added to every instance of a Portal.
Web
Install the Ionic Portals package from NPM into your web application.
npm install @ionic/portals
Initial Context
The Initial Context mechanism allows you to pass data to your web application from native so that it is available for when the web application initially loads.
Setting Initial Context
Initial context can be set during initialization:
- Swift
- Objective-C
let portal = Portal(
name: "maps",
startDir: "web",
initialContext: ["ic_example": "hello world"]
)
IONPortal *portal = [[IONPortal alloc] initWithName:@"maps" startDir:@"web" initialContext:@{ @"ic_example": @"hello world" }];
Or after:
- Swift
- Objective-C
var portal = Portal(name: "maps", startDir: "web")
portal.initialContext = ["ic_example": "hello world"]
IONPortal *portal = [[IONPortal alloc] initWithName:@"maps" startDir:@"web" initialContext:nil];
portal.initialContext = @{ @"ic_example": @"hello world" };
Using Initial Context
To access the initial context set from the native application in your web application, import getInitialContext
from @ionic/portals
use the getInitialContext() function.
import { getInitialContext } from "@ionic/portals";
const initialContext = getInitialContext<{ ic_example: string }>();
// prints "hello world" in this example
console.log(initialContext?.value?.ic_example);
Initial context is useful when using a Single Page Application (SPA) across multiple Portals in your application. The route to a specific section of the SPA can be passed in as initial context data. Your web application can then use it to load that section directly without need for a redirect. Check out our how-to guide.
Communicating via Pub/Sub
The Publish and Subscribe mechanism (pub/sub) built into the PortalsPlugin
allows you to send data between your web and native applications through a Portal.
Defining Subscribers
Subscribers listen for messages sent to a certain topic. They can be defined in your web application to listen for messages published from native, and vice versa.
To listen for a message published from the native side of a Portal, define a subscriber in your web application.
const portalSubscription = await Portals.subscribe({ topic }, (result) => {
console.log(JSON.stringify(result));
});
To listen for messages published from the web side of a Portal, define a subscriber in your native application.
- Swift (Combine)
- Swift (async/await)
- Swift (vanilla)
- Objective-C
import UIKit
import IonicPortals
class MyViewController: UIViewController {
var dismissCancellable: AnyCancellable?
override func viewDidLoad() {
dismissCancellable = PortalsPubSub.publisher(for: "dismiss")
.data(as: String.self)
.filter { $0 == "cancel" || $0 == "success" }
.receive(on: DispatchQueue.main)
.sink { _ in self.dismiss(animated: true, completion: nil) }
super.viewDidLoad()
}
}
import SwiftUI
import IonicPortals
struct CartView: View {
@State private var shouldDisplayCheckout = false
var body: some View {
VStack {
// Cart contents
Button("Checkout") {
shouldDisplayCheckout = true
}
}
.sheet(isPresented: $shouldDisplayCheckout) {
PortalView(portal "checkout")
}
.onReceive(
PortalsPubSub.publisher(for: "dismiss")
.data(as: String.self)
.filter { $0 == "cancel" || $0 == "success" }
) { _ in
shouldDisplayCheckout = false
}
}
import UIKit
import IonicPortals
class MyViewController: UIViewController {
var task: Task?
override func viewDidLoad() {
task = Task {
let _ = await PortalsPubSub.subscribe("dismiss")
.flatMap { $0.data as? String }
.filter { $0 == "cancel" || $0 == "success" }
.first
dismiss(animated: true, completion: nil)
}
}
deinit {
task?.cancel()
}
}
import SwiftUI
import IonicPortals
struct CartView: View {
@State private var shouldShowModal = false
var body: some View {
VStack {
Button("Checkout") {
shouldShowModal = true
}
}
.sheet(isPresented: $shouldShowModal) {
PortalView(portal: "checkout")
}
.task {
let eventStream = PortalsPubSub.subscribe(to: "dismiss")
.compactMap { $0.data as? String }
for await event in eventStream
where event == "cancel" || event == "success" {
shouldDisplayCheckout = false
}
}
}
}
import UIKit
class MyViewController: UIViewController {
var subscriptionReference: Int?
override func viewDidLoad() {
subscriptionReference = PortalsPubSub
.subscribe("dismiss") { [weak self] result in
guard
let message = result.data as? String,
message == "cancel" || message == "sucess"
else { return }
self?.dismiss(animated: true, completion: nil)
}
super.viewDidLoad()
}
deinit {
// Required to prevent closure to continue firing
PortalsPubSub.unsubscribe(from: "dismiss", subscriptionRef: subscriptionReference)
}
}
There is also an overload that returns an AnyCancellable
so that manually calling PortalsPubSub.unsubscribe(from:subscriptionRef:)
is not needed:
import UIKit
import Combine // import only needed for AnyCancellable visibility
class MyViewController: UIViewController {
var dismissCancellable: AnyCancellable?
override func viewDidLoad() {
dismissCancellable = PortalsPubSub
.subscribe(to: "dismiss") { [weak self] result in
guard
let message = result.data as? String,
message == "cancel" || message == "sucess"
else { return }
self?.dismiss(animated: true, completion: nil)
}
super.viewDidLoad()
}
}
@interface MyViewController ()
@property NSInteger subRef;
@end
@implementation MyViewController
- (void)viewDidLoad {
self.subRef = [IONPortalsPubSub subscribeToTopic:@"dismiss" callback:^(NSDictionary<NSString *,id> * _Nonnull result) {
NSString *message = result[@"data"];
if (message != nil && ([message isEqualToString:@"success"] || [message isEqualToString:@"cancel"])) {
[self dismissViewControllerAnimated:YES completion:nil];
}
}];
[super viewDidLoad];
}
- (void)dealloc {
[IONPortalsPubSub unsubscribeFromTopic:@"dismiss" subscriptionRef:self.subRef];
}
@end
Publishing Messages
Publish messages to send data through a Portal to registered Subscribers.
From Web to iOS
To send a message from your web application to iOS or Android, use the Portals.publish() function.
Portals.publish({ topic: "dismiss", data: "success" });
From iOS to Web
To send messages from your native application to the web application, use the PortalsPubSub.publish()
method.
- Swift
- Objective-C
PortalsPubSub.publish("sunny" to: "weather")
[IONPortalsPubSub publishMessage:@"sunny" toTopic:@"weather"];
Examples
The PortalsPlugin
is used in the E-Commerce App demo.