PNLight SDK - Flutter Plugin
A Flutter plugin that provides iOS integration with the PNLight SDK.
Installation
Add this to your package's pubspec.yaml file:
dependencies:
pnlight_sdk: ^0.1.0
Then run:
flutter pub get
Requirements
- Flutter: >=3.3.0
- Dart: >=2.17.0 <4.0.0
- iOS: 15.0+
This package is iOS-only. Android is not supported.
iOS Setup
pnlight_sdk declares DivKit, the DivKit extension runtime, Lottie, and Core
Haptics transitively. After adding the Flutter package, no separate pod,
renderer registration, navigation controller, or safe-area integration is
required for RemoteUiView.
The pod also links PNLight's required Apple frameworks automatically. Apps that
need IDFA tracking must still provide the user-facing
NSUserTrackingUsageDescription text in Info.plist:
<key>NSUserTrackingUsageDescription</key>
<string>This app uses device tracking to provide analytics and improve user experience.</string>
Usage
Initialization
Initialize PNLight before using analytics, attribution, or Remote UI:
import 'package:flutter/widgets.dart';
import 'package:pnlight_sdk/pnlight_sdk.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await PNLightSDK.initialize('your-api-key');
runApp(MyApp());
}
Event Logging
import 'package:pnlight_sdk/pnlight_sdk.dart';
await PNLightSDK.logEvent('purchase_completed', {
'product_id': 'premium_subscription',
'amount': 9.99,
'currency': 'USD',
});
Attribution
Send attribution data from external providers before requesting UI config.
import 'package:pnlight_sdk/pnlight_sdk.dart';
final success = await PNLightSDK.addAttribution(
provider: 'appsFlyer',
data: {'af_status': 'Non-organic'},
identifier: 'your-appsflyer-id',
);
AppsFlyer Integration Example
PNLight does not depend on the AppsFlyer initialization order — it only needs the conversion data, delivered via addAttribution. Make sure the conversion ("attribution success") callback is not processed before PNLight is initialized: if it can fire earlier, store the conversion data in memory and call addAttribution once initialize completes.
AppsFlyer requires the ATT prompt to complete before it starts. If PNLight is initialized before ATT authorization, call updateIdfa() after the prompt completes so PNLight receives the granted IDFA.
import 'package:app_tracking_transparency/app_tracking_transparency.dart';
import 'package:appsflyer_sdk/appsflyer_sdk.dart';
import 'package:pnlight_sdk/pnlight_sdk.dart';
Future<void> startSdks() async {
await PNLightSDK.initialize('your-api-key');
// Request ATT, then pass the granted IDFA to PNLight.
await AppTrackingTransparency.requestTrackingAuthorization();
await PNLightSDK.updateIdfa();
// Start AppsFlyer after ATT completes (an AppsFlyer requirement).
final appsFlyerSdk = AppsflyerSdk(AppsFlyerOptions(
afDevKey: 'your-appsflyer-dev-key',
appId: 'your-ios-app-id',
showDebug: false,
));
appsFlyerSdk.onInstallConversionData((conversionData) async {
final appsFlyerId = await appsFlyerSdk.getAppsFlyerUID();
final payload = conversionData['payload'] ?? conversionData;
await PNLightSDK.addAttribution(
provider: 'appsFlyer',
data: Map<String, dynamic>.from(payload as Map),
identifier: appsFlyerId,
);
});
await appsFlyerSdk.initSdk(
registerConversionDataCallback: true,
registerOnAppOpenAttributionCallback: false,
registerOnDeepLinkingCallback: false,
);
}
Supported providers:
appsFlyerfirebasefacebook
User Identity
import 'package:pnlight_sdk/pnlight_sdk.dart';
final userId = await PNLightSDK.getUserId();
In-App Purchases
PNLight wraps StoreKit 2 for fetching products (price, offers, trial info),
purchasing, restoring, and checking entitlements. The product ids are configured
on the backend — fetchProducts resolves them against the App Store.
import 'package:pnlight_sdk/pnlight_sdk.dart';
// Load the configured products with their App Store price/offer info.
final products = await PNLightSDK.fetchProducts();
for (final product in products) {
print('${product.displayName}: ${product.displayPrice}');
final offer = product.subscription?.introductoryOffer;
if (offer != null && product.subscription!.isEligibleForIntroOffer) {
// e.g. pay-as-you-go: "$0.99/month for 6 months"
print('Offer: ${offer.displayPrice} (${offer.paymentMode.name}, '
'${offer.periodCount} × ${offer.period.value} ${offer.period.unit.name})');
}
}
// Purchase.
final result = await PNLightSDK.purchase('your.product.id');
if (result == PNLightPurchaseResult.success) {
// Unlock content.
}
// Entitlement checks (local StoreKit entitlements, work offline).
final premium = await PNLightSDK.isPremium();
final eligible = await PNLightSDK.isEligibleForTrial('your.product.id');
// Restore previous purchases.
await PNLightSDK.restorePurchases();
RemoteUiView - Server-driven UI
RemoteUiView fetches and renders a server-driven layout from PNLight for a given placement. It calls getUIConfig(placement) internally, renders the native view, and emits action events to Dart.
DivKit markup can also select PNLight's three custom native iOS components:
pnlight.circular_loader, pnlight.cta_button, and
pnlight.icon_button. They are UIKit views rendered inside the iOS platform
view; Dart does not recreate them. Their layout, styling, state, and actions are
still controlled by the server-delivered DivKit JSON. These custom components
are unavailable on non-iOS platforms.
When using external attribution providers such as AppsFlyer, send attribution as early as possible (see the AppsFlyer example above). getUIConfig waits for attribution data internally when attributionRequired is true (the default), so no manual delay is needed.
import 'package:flutter/widgets.dart';
import 'package:pnlight_sdk/pnlight_sdk.dart';
class PaywallScreen extends StatelessWidget {
const PaywallScreen({super.key});
void _handleAction(RemoteUiActionEvent event) {
if (event.logId == 'purchase_button') {
final productId = event.params['id'];
// Start purchase flow for productId
} else if (event.logId == 'close_button') {
// Close the screen
}
}
@override
Widget build(BuildContext context) {
return RemoteUiView(
placement: 'paywall',
onAction: _handleAction,
);
}
}
Remote UI schema version
PNLight versions the Remote UI envelope independently from the pub package.
Declare "schemaVersion": 2 at the document root to enable PNLight safe-area
handling, linear-scaling variables, server-driven flows, native haptics, and
native dialogs:
{
"schemaVersion": 2,
"card": {}
}
A missing schemaVersion (or an explicit value of 1) is legacy v1. Existing
backend configs therefore keep their previous edge-to-edge DivKit behavior
when an app upgrades the plugin. They do not receive v2 scaling variables or
opt into PNLight-owned navigation, haptic, and dialog action handling. New
configs that use any feature below should declare "schemaVersion": 2.
Safe areas
Remote UI stays clear of the Dynamic Island, status bar, home indicator, and
landscape sensor housing without Flutter-side padding in schema v2. A v2
document with no safe_area field defaults to inset on all four edges:
{
"schemaVersion": 2,
"safe_area": {
"mode": "inset",
"edges": ["top", "bottom", "left", "right"]
},
"card": {}
}
| Mode | Behavior |
|---|---|
inset |
Default. Places the complete DivKit document inside the selected safe-area edges. |
content |
Keeps the document edge-to-edge and exposes selected insets as DivKit variables. |
edge_to_edge |
Keeps the document edge-to-edge and resolves safe-area variables to zero. |
In content mode, use safe_area_top, safe_area_bottom,
safe_area_left, and safe_area_right in the content container while its
background remains full bleed:
{
"schemaVersion": 2,
"safe_area": {
"mode": "content",
"edges": ["top", "bottom"]
},
"card": {
"states": [{
"state_id": 0,
"div": {
"type": "container",
"width": { "type": "match_parent" },
"height": { "type": "match_parent" },
"paddings": {
"top": "@{safe_area_top + 24}",
"bottom": "@{safe_area_bottom + 24}",
"left": 24,
"right": 24
},
"items": []
}
}]
}
}
PNLight updates these variables after rotation, window resizing, and sheet
presentation. A flow can declare a default safe_area; each route can override
it beside divkit and presentation.
Linear scaling variables
Remote UI JSON may declare a logical design viewport:
{
"schemaVersion": 2,
"referenceSize": { "width": 390, "height": 844 },
"card": {}
}
PNLight exposes scaleX = availableWidth / referenceSize.width and
scaleY = availableHeight / referenceSize.height as live numeric DivKit
variables. Use them directly in expressions, for example
"left": "@{24 * scaleX}". They update for rotation, host-view resize, and
native sheet-size changes. In inset safe-area mode, the available viewport is
measured after the selected system insets are removed.
The ratios are raw, not automatically clamped, and PNLight does not scale the
layout unless markup references them. For a uniform mobile design scale,
prefer scaleX and apply your chosen bounds in markup. Never multiply system
safe-area or keyboard insets by these values. Without referenceSize, both
variables equal 1 in schema v2.
Flows may define referenceSize beside routes; individual routes may
override it beside divkit or inside their complete divkit document. No
Flutter configuration is required.
Server-driven native flows
The same RemoteUiView also accepts a PNLight flow envelope. Each route contains
a complete DivKit document. PNLight owns a private native navigation stack and
consumes navigation URLs before they reach Dart:
{
"schemaVersion": 2,
"type": "flow",
"safe_area": { "mode": "inset" },
"initial_route": "welcome",
"routes": {
"welcome": {
"divkit": {
"templates": {},
"card": {
"log_id": "welcome",
"states": [{
"state_id": 0,
"div": {
"type": "custom",
"custom_type": "pnlight.cta_button",
"width": { "type": "match_parent" },
"height": { "type": "fixed", "value": 56 },
"custom_props": {
"title": "Open offer",
"url": "pnlight://navigation/present?route=offer"
}
}
}]
}
}
},
"offer": {
"safe_area": {
"mode": "content",
"edges": ["top", "bottom"]
},
"presentation": {
"style": "sheet",
"detent": "large",
"grabber": true,
"dismissible": true,
"corner_radius": 28
},
"divkit": {
"templates": {},
"card": {
"log_id": "offer",
"states": [{
"state_id": 0,
"div": {
"type": "custom",
"custom_type": "pnlight.cta_button",
"width": { "type": "match_parent" },
"height": { "type": "fixed", "value": 56 },
"custom_props": {
"title": "Dismiss",
"url": "pnlight://navigation/dismiss"
}
}
}]
}
}
}
}
}
| URL | Native iOS behavior |
|---|---|
pnlight://navigation/push?route=details |
Pushes a route on the active PNLight stack. |
pnlight://navigation/pop |
Pops the active stack. |
pnlight://navigation/replace?route=details |
Replaces the active route. |
pnlight://navigation/pop_to_root |
Pops to the first route. |
pnlight://navigation/present?route=offer |
Presents a route in a new native modal navigation context. |
pnlight://navigation/dismiss |
Dismisses the current PNLight modal context. |
Routes are preloaded when the flow is received. A presented route can push other routes inside its own stack, while dismissing it preserves the embedded stack's controllers, DivKit variables, and scroll state.
presentation.style accepts sheet (default) or full_screen. A sheet uses
the native iOS page-sheet presentation:
| Field | Default | Description |
|---|---|---|
detent |
large |
large, or medium with expansion to large. |
grabber |
true |
Shows the native sheet grabber. |
dismissible |
true |
When false, disables swipe-to-dismiss. |
corner_radius |
system | Preferred native sheet corner radius. |
The present URL can override those fields for one transition, for example
pnlight://navigation/present?route=offer&detent=medium&grabber=false.
Unknown routes are ignored. In a legacy single-card document, navigation URLs
continue through onAction; they are consumed only inside a valid flow.
Native alerts and action sheets
Put dialogs at the Remote UI document root (beside card, or beside routes
in a flow) and trigger one with
pnlight://dialog/show?id=<dialog-id>. The plugin presents a native iOS
UIAlertController; Flutter needs no navigator, dialog state, or additional
configuration.
{
"schemaVersion": 2,
"dialogs": {
"delete_confirmation": {
"style": "alert",
"title": "Delete item?",
"message": "This cannot be undone.",
"buttons": [
{ "title": "Cancel", "style": "cancel", "actions": [] },
{
"title": "Delete",
"style": "destructive",
"actions": [
{
"log_id": "delete_confirmed",
"url": "my-app://delete?id=42"
}
]
}
]
}
},
"card": {}
}
Dialog style is alert or action_sheet; button style is default,
cancel, or destructive. Every button owns an ordered array of normal
DivKit actions. They run through the current card's DivKit handler, including
typed variable/state actions, PNLight navigation and haptics, analytics, and
custom URLs delivered through onAction. Empty arrays are dismiss-only.
Native haptics
PNLight consumes pnlight://haptic/... actions before onAction. Simple UIKit
feedback works in both schema-v2 single-card documents and flow routes:
| URL | Native behavior |
|---|---|
pnlight://haptic/impact?style=light |
Impact feedback. style: light, medium, heavy, soft, or rigid; optional intensity is clamped to 0...1. |
pnlight://haptic/selection |
Selection feedback. |
pnlight://haptic/notification?type=success |
Notification feedback. type: success, warning, or error. |
Flows can also declare named Core Haptics patterns:
{
"schemaVersion": 2,
"type": "flow",
"initial_route": "main",
"haptics": {
"ambient_pulse": {
"events": [
{
"type": "continuous",
"time": 0,
"duration": 0.8,
"intensity": 0.25,
"sharpness": 0.35
},
{
"type": "transient",
"time": 0.4,
"intensity": 0.65,
"sharpness": 0.55
}
],
"loop": true,
"max_duration": 120
}
},
"routes": {
"main": {
"divkit": {
"templates": {},
"card": {
"log_id": "main",
"states": [{
"state_id": 0,
"div": {
"type": "text",
"text": "Haptic demo",
"width": { "type": "match_parent" },
"height": { "type": "wrap_content" }
}
}]
}
}
}
}
}
Use pnlight://haptic/start?pattern=ambient_pulse to start a named pattern and
pnlight://haptic/stop?pattern=ambient_pulse to stop it. A stop URL without
pattern stops every active player owned by the route.
Patterns accept 1–128 transient or continuous events. Timing is in seconds;
time_ms, duration_ms, and max_duration_ms are also accepted. Intensity
and sharpness are clamped to 0...1. A pattern can run for at most 600 seconds
and defaults to a five-minute safety limit. Core Haptics is a no-op on
unsupported hardware. Active players stop when their route leaves the window,
the configuration changes, the app enters the background, or the renderer is
destroyed.
Lottie animations
pnlight_sdk registers DivKit's standard lottie extension automatically.
The Flutter app does not install Lottie or register a renderer.
Attach the extension to a fixed-size DivKit element and provide a remote JSON URL:
{
"type": "container",
"width": { "type": "fixed", "value": 200 },
"height": { "type": "fixed", "value": 200 },
"items": [],
"extensions": [{
"id": "lottie",
"params": {
"lottie_url": "https://cdn.example.com/animation.json",
"repeat_count": 0,
"repeat_mode": "restart",
"is_playing": true
}
}]
}
Alternatively, put the decoded Lottie document directly in lottie_json:
{
"id": "lottie",
"params": {
"lottie_json": {
"v": "5.7.4",
"fr": 60,
"ip": 0,
"op": 120,
"w": 200,
"h": 200,
"assets": [],
"layers": []
}
}
}
repeat_mode accepts restart (default) or reverse. A repeat_count of 0
repeats indefinitely, and is_playing defaults to true. Remote URLs use the
DivKit resource pipeline; use HTTPS in production. The integration supports
inline and downloaded uncompressed Lottie JSON, but not .lottie ZIP archives.
Native iOS circular loader
Use DivKit's custom element to render a native UIActivityIndicatorView inside Remote UI markup:
{
"type": "custom",
"custom_type": "pnlight.circular_loader",
"width": { "type": "fixed", "value": 48 },
"height": { "type": "fixed", "value": 48 },
"custom_props": {
"style": "large",
"color": "#FF007AFF",
"accessibility_label": "Loading"
}
}
style accepts "medium" (the default) or "large". color accepts #RRGGBB or DivKit-style #AARRGGBB and defaults to the adaptive iOS label color. accessibility_label defaults to "Loading". Standard DivKit width and height fields control the element's layout; set a dimension to { "type": "wrap_content" } to use the native indicator's intrinsic size for that dimension.
Native iOS CTA button
Render a native, animated call-to-action button with a repeating shimmer streak,
an idle attention pulse, and a spring press bounce. Taps use the same action
pipeline as DivKit buttons, so a custom-scheme url fires
RemoteUiView.onAction with a RemoteUiActionEvent.
{
"type": "custom",
"custom_type": "pnlight.cta_button",
"width": { "type": "match_parent" },
"height": { "type": "fixed", "value": 58 },
"custom_props": {
"title": "Continue",
"background_color": "#FF007AFF",
"title_color": "#FFFFFFFF",
"corner_radius": 16,
"font_size": 19,
"font_weight": "bold",
"shimmer": true,
"bounce": true,
"url": "pnlight://cta?id=continue"
}
}
All props are optional; only title and url are usually needed. Colors accept
#RRGGBB or DivKit-style #AARRGGBB.
| Prop | Default | Description |
|---|---|---|
title |
"" |
Button label. |
background_color |
#FF007AFF |
Fill color (also the gradient start). |
background_color_end |
— | When set, the fill is a horizontal gradient to this color. |
glass |
off (on for icon buttons) | Native Liquid Glass fill on iOS 26+. See below. |
title_color |
#FFFFFFFF |
Label color. |
corner_radius |
14 |
Corner radius in points (continuous curve). |
font_size |
18 |
Label point size. |
font_weight |
semibold |
regular/medium/semibold/bold/heavy/black. |
horizontal_padding / vertical_padding |
24 / 16 |
Used to size the button when width/height is wrap_content. |
icon |
— | SF Symbol name, e.g. "shield.lefthalf.filled". Renders before the title. |
icon_size |
20 |
Symbol point size. |
icon_weight |
semibold |
ultralight … black. |
icon_color |
title_color |
Symbol tint. |
icon_spacing |
8 |
Gap between icon and title. |
loading |
false |
Swaps the title for a native spinner and ignores taps. |
disabled |
false |
Dims the button and ignores taps. |
disabled_alpha |
0.45 |
Opacity used while disabled. |
disabled_background_color |
— | Replaces the fill (and any gradient) while disabled. |
disabled_title_color |
— | Replaces the label color while disabled. |
loading_indicator_color |
title_color |
Spinner color. |
loading_indicator_style |
medium |
medium or large. |
url |
— | Action fired on tap (custom scheme → onAction; http(s) → opened externally). |
log_id |
— | Emitted as the action's logId when there is no url. |
accessibility_label |
title |
VoiceOver label (defaults to "Loading" while loading). |
Both loading and disabled make the button inert: taps are ignored and the
shimmer and bounce animations stop.
shimmer and bounce accept a boolean shorthand (true/false) or an object
for fine control:
{
"shimmer": {
"enabled": true,
"color": "#80FFFFFF",
"duration": 1.4,
"pause": 1.1,
"band_width": 0.3,
"angle": 16
},
"bounce": {
"idle": { "enabled": true, "scale": 1.03, "period": 2.6 },
"press": { "enabled": true, "scale": 0.96 }
}
}
{ "bounce": true } enables both the idle pulse and press feedback.
{ "bounce": { "idle": false, "press": true } } keeps only press feedback.
Liquid Glass (iOS 26+)
Buttons can use the native Liquid Glass material. Icon buttons opt in
automatically when markup does not set background_color; they fall back to
secondarySystemFill on older systems. An explicit background_color disables
the automatic glass default. Set glass explicitly to combine a chosen color
with the material:
{
"glass": {
"enabled": true,
"style": "regular",
"tint": "#99FF375F",
"prominent": true
}
}
style accepts "regular" or "clear". { "glass": true } is the shorthand
for opting a CTA in. prominent selects the filled primary-action treatment.
When glass is enabled, title_color defaults to the adaptive label color and
shimmer defaults to off unless markup overrides them. Glass falls back to the
solid background_color on iOS 25 and earlier, when Reduce Transparency is
enabled, and while disabled_background_color applies.
Native CTA and icon buttons also support the standard DivKit actions array.
Actions execute in declaration order through DivKit's normal action handler,
including typed variable/state actions, PNLight haptics, dialogs, navigation,
analytics, and custom URLs. A non-empty actions array takes precedence over
the custom_props.url / log_id single-action shorthand.
Driving loading and disabled at runtime
Any custom prop can contain a DivKit expression. For example, bind loading to
a card variable:
{
"card": {
"variables": [{ "type": "boolean", "name": "is_busy", "value": false }],
"states": [{ "state_id": 0, "div": {
"type": "custom",
"custom_type": "pnlight.cta_button",
"custom_props": {
"title": "Submit",
"loading": "@{is_busy}",
"url": "pnlight://cta?id=submit"
}
}}]
}
}
Toggle the variable from markup with a standard set_variable action:
{
"log_id": "begin",
"typed": {
"type": "set_variable",
"variable_name": "is_busy",
"value": { "type": "boolean", "value": true }
}
}
The Flutter callback receives the URL query parameters:
void handleAction(RemoteUiActionEvent event) {
if (event.params['id'] == 'continue') {
// Continue the flow.
}
}
RemoteUiView(
placement: 'paywall',
onAction: handleAction,
);
Native iOS icon button
pnlight.icon_button uses the same native button renderer, tuned for a small
circular SF Symbol control such as close, settings, or favorite:
{
"type": "custom",
"custom_type": "pnlight.icon_button",
"width": { "type": "fixed", "value": 44 },
"height": { "type": "fixed", "value": 44 },
"custom_props": {
"icon": "xmark",
"accessibility_label": "Close",
"url": "pnlight://cta?id=close"
}
}
It accepts every pnlight.cta_button prop above, including loading,
disabled, shimmer, bounce, and expression binding. Only its defaults
differ:
| Prop | CTA default | Icon default |
|---|---|---|
| shape | corner_radius: 14 |
fully circular |
background_color |
#FF007AFF |
secondarySystemFill (adaptive) |
glass |
off | on when no background_color is set |
| icon/title color | white | label (adaptive) |
horizontal_padding / vertical_padding |
24 / 16 |
12 / 12 |
shimmer |
on | off |
bounce.idle |
on | off |
bounce.press |
on | on |
Setting corner_radius opts out of the circular shape. With wrap_content on
both axes, the button sizes itself from the symbol plus padding and remains
square.
For an icon-only button, set accessibility_label; otherwise VoiceOver falls
back to the SF Symbol name. An unknown symbol name is reported in the card's
DivKit errors instead of silently rendering an empty button.
Manual Config Fetching
Use getUIConfig if you need to fetch the placement configuration yourself. When attributionRequired is true (the default), the SDK waits for attribution data internally before returning:
import 'package:pnlight_sdk/pnlight_sdk.dart';
final config = await PNLightSDK.getUIConfig('paywall');
final configWithoutAttributionWait = await PNLightSDK.getUIConfig(
'paywall',
attributionRequired: false,
);
API Reference
PNLightSDK
| Method | Description |
|---|---|
initialize(apiKey, baseDomain?) |
Initialize the SDK with your API key and optional base domain |
logEvent(eventName, eventArgs?) |
Log a custom event with optional arguments |
addAttribution(provider, data?, identifier?) |
Send attribution data from AppsFlyer, Firebase, or Facebook |
getUserId() |
Get or create a stable user identifier |
updateIdfa() |
Send the current IDFA to PNLight after the ATT prompt completes |
prefetchUIConfig(placement) |
Prefetch a UI config into the in-memory cache |
getUIConfig(placement, attributionRequired?) |
Fetch a UI config; waits for attribution by default |
fetchProducts() |
Load configured products with App Store price/offer/trial info |
purchase(productId) |
Purchase a product; returns a PNLightPurchaseResult |
restorePurchases() |
Restore previous purchases by syncing with the App Store |
isPremium() |
Whether the user has an active entitlement to any configured product |
isPurchased(productId) |
Whether the user has an active entitlement to a specific product |
isEligibleForTrial(productId) |
Whether the user is eligible for a product's introductory offer |
getAppleReceipt() |
Base64 App Store receipt for server-side validation, or null if absent |
RemoteUiView
| Parameter | Type | Description |
|---|---|---|
placement |
String |
PNLight placement identifier |
cardId |
String? |
Card identifier; defaults to pnlight_<placement> |
secure |
bool |
Deprecated. Secure rendering is controlled by the backend response. |
preventRecording |
bool |
Deprecated. Capture blocking is controlled by the backend. |
onAction |
void Function(RemoteUiActionEvent)? |
Called when a custom action is triggered |
RemoteUiActionEvent
| Property | Type | Description |
|---|---|---|
url |
String |
Full URL string of the triggered action |
scheme |
String |
URL scheme |
path |
String |
URL path component |
params |
Map<String, String> |
Query parameters extracted from the URL |
logId |
String? |
Log ID for the triggered action |
action |
String? |
Raw action value when provided by the native view |
UIConfig
| Property | Type | Description |
|---|---|---|
config |
String? |
Remote UI JSON config |
parameters |
Map<String, dynamic>? |
Placement parameters returned by PNLight |
debug |
bool? |
Debug flag returned by PNLight |
Support
For support and questions, visit docs.pnlight.app.