An inline in-app message is a component that you embed directly into your app's view hierarchy — for example, a banner between list sections, a tile on the home screen, a menu, or even an entire view. They allow you to manipulate content in the application without changing the code of the app.
| iOS SDK | Android SDK | React Native SDK | Flutter SDK | |
|---|---|---|---|---|
| Introduced in: | 6.0.0 | 6.15.0 | 2.0.1 | 3.0.0 |
NOTE: Android SDK 6.16.0 or newer is recommended; the URL and deep-link callbacks changed in 6.16.0.
Trigger modes
Inline in-app campaigns support three trigger modes, configured in the campaign settings:
- Automatic — the in-app message is rendered automatically when a view with the associated placement (container ID) appears on the screen.
- On demand — the in-app message is rendered when the application calls the SDK method.
- Event — the in-app message is rendered when a specified event occurs in the application.
In terms of the SDK behavior:
- For Automatic, the SDK shows the content as soon as the component is added to the view hierarchy.
- For On demand, the application calls
render()to trigger content rendering. - For Event, if a component for the placement is already embedded, the SDK updates its content. Otherwise, the SDK creates a new component and delivers it through the global listener/delegate (if none is registered,the campaign is dropped).
Trigger modes are exclusive. render() only renders campaigns with the On demand trigger. An Automatic campaign never replaces content that is already displayed in the view; On demand and Event campaigns do, subject to priority.
Multiple views and campaign priority
Several InlineInAppView instances can share the same placement key. Each instance receives the campaign independently: inApp.show and inApp.discard events are reported per view. On Android, capping is counted once per render of the placement; on iOS, every view counts separately, so two views of one placement consume two displays of the capping limit.
When more than one campaign matches a placement, the campaign with the highest priority wins (lower number = higher priority). A campaign that is already displayed is not replaced by another campaign of equal or lower priority; only a strictly higher priority (lower number) replaces it, and the per-component callback receives the onUpdated / snr_inlineInAppViewDidUpdate event.
Controlling behavior and actions
Inline in-app messages use two types of callbacks:
- Per-component listener/delegate handles the lifecycle of an individual component: loading, updating, size changes, and removal requests. You set it on each
InlineInAppViewinstance; in React Native these are properties of the component. - Global listener/delegate handles action callbacks: URL, deeplink, custom action, custom method; and delivers SDK-created components for event-based triggers. You set it once at the application level.
You can handle the per-component lifecycle using:
- OnInlineInAppViewListener methods for Android
- InlineInAppViewDelegate methods for iOS
- InlineInAppView callback props for React Native
- InlineInAppView callbacks for Flutter
You can handle actions and event-based delivery using:
- OnInlineInAppListener methods for Android, registered with
Injector.setOnInlineInAppListenerand removed withInjector.removeInlineInAppListener - InjectorInlineInAppMessageDelegate methods for iOS, registered with
Injector.setInlineInAppMessageDelegate(weak reference; passnilto remove) - IInlineInAppMessageListener methods for React Native, registered with
Synerise.Injector.setInlineInAppMessageListener - InjectorInlineInAppMessageListener methods for Flutter, registered with
Synerise.injector.inlineInAppMessageListenerand removed withSynerise.injector.removeInlineInAppListener
See the following code samples for the per-component delegate/listener:
inlineView.setOnInlineInAppViewListener(new OnInlineInAppViewListener() {
// This method is called when the SDK starts processing a campaign for this component.
// Not called when no campaign matches the placement.
@Override
public void onProcessingStarted() {
//...
}
// This method is called when the inline in-app message content is loaded for the first time.
@Override
public void onLoaded(InlineInAppMessageData data) {
//...
}
// This method is called when the inline in-app message content is updated with a new campaign.
@Override
public void onUpdated(InlineInAppMessageData data) {
//...
}
// This method is called when the inline in-app message content fails to load or render.
// data may be null (for example "No campaign found for placement key=..."). See Failures below.
@Override
public void onFailed(InlineInAppMessageData data, String error) {
//...
}
// This method is called when the in-app message content requests a size change
// via the SRInApp.setComponentSize(width, height) JS method.
@Override
public void onSizeChanged(InlineInAppMessageData data, InlineInAppSize size) {
//...
}
// This method is called when the in-app message content requests removal from the view hierarchy
// via the SRInApp.close() or SRInApp.closeAndTrigger() JS method.
// Also called when the app closes the campaign with Injector.closeInAppMessage(campaignHash).
@Override
public void onRemove(InlineInAppMessageData data) {
//...
}
});
inlineView.setOnInlineInAppViewListener(object : OnInlineInAppViewListener {
// This method is called when the SDK starts processing a campaign for this component.
// Not called when no campaign matches the placement.
override fun onProcessingStarted() {
//...
}
// This method is called when the inline in-app message content is loaded for the first time.
override fun onLoaded(data: InlineInAppMessageData) {
//...
}
// This method is called when the inline in-app message content is updated with a new campaign.
override fun onUpdated(data: InlineInAppMessageData) {
//...
}
// This method is called when the inline in-app message content fails to load or render.
// data may be null (for example "No campaign found for placement key=..."). See Failures below.
override fun onFailed(data: InlineInAppMessageData?, error: String) {
//...
}
// This method is called when the in-app message content requests a size change
// via the SRInApp.setComponentSize(width, height) JS method.
override fun onSizeChanged(data: InlineInAppMessageData, size: InlineInAppSize) {
//...
}
// This method is called when the in-app message content requests removal from the view hierarchy
// via the SRInApp.close() or SRInApp.closeAndTrigger() JS method.
// Also called when the app closes the campaign with Injector.closeInAppMessage(campaignHash).
override fun onRemove(data: InlineInAppMessageData) {
//...
}
})
// MARK: - InlineInAppViewDelegate
// Assigned with inlineView.delegate = self (weak reference). All methods are called on the main thread.
// This method is called when the SDK starts processing a campaign for this component.
func snr_inlineInAppViewDidStartProcessing(view: InlineInAppView) {
//...
}
// This method is called when the inline in-app message content is loaded for the first time.
// Not called for views delivered through the global delegate; they arrive already loaded.
func snr_inlineInAppViewDidLoad(view: InlineInAppView, data: InlineInAppMessageData) {
//...
}
// This method is called when the inline in-app message content is updated with a new campaign.
func snr_inlineInAppViewDidUpdate(view: InlineInAppView, data: InlineInAppMessageData) {
//...
}
// This method is called when the inline in-app message content fails to load or render.
// error.code is one of the SNRErrorCode values listed under Failures below.
func snr_inlineInAppViewDidFail(view: InlineInAppView, error: NSError) {
//...
}
// This method is called when the in-app message content requests a size change
// via the SRInApp.setComponentSize(width, height) JS method.
func snr_inlineInAppViewChangeSizeIsNeeded(view: InlineInAppView, data: InlineInAppMessageData, size: InlineInAppSize) {
//...
}
// This method is called when the in-app message content requests removal from the view hierarchy
// via the SRInApp.close() or SRInApp.closeAndTrigger() JS method.
func snr_inlineInAppViewShouldBeRemoved(view: InlineInAppView, data: InlineInAppMessageData) {
//...
}
import { InlineInAppView } from 'react-native-synerise-sdk';
// The per-component callbacks are properties. The component has no intrinsic size: give it a height.
<InlineInAppView
ref={inlineRef}
placementKey="home_top_banner"
style={{ width: '100%', height: 100 }}
// Called when the SDK starts processing a campaign for this component.
onProcessingStarted={() => {}}
// Called when the content is loaded for the first time.
onLoaded={(data) => {}}
// Called when the content is updated with a new campaign.
onUpdated={(data) => {}}
// Called when the content fails to load or render.
onFailed={(data, error) => {}}
// Called when the content requests a size change. size.height is already in layout units.
onSizeChanged={(data, size) => {}}
// Called when the content requests removal. Unmount the component to remove it.
onRemove={(data) => {}}
/>
InlineInAppView(
placementKey: 'home_top_banner',
// This method is called when the SDK starts processing a campaign for this component.
onProcessingStarted: () {
//...
},
// This method is called when the inline in-app message content is loaded for the first time.
onLoaded: (data) {
//...
},
// This method is called when the inline in-app message content is updated with a new campaign.
onUpdated: (data) {
//...
},
// This method is called when the inline in-app message content fails to load or render.
onFailed: (data, error) {
//...
},
// This method is called when the in-app message content requests a size change
// via the SRInApp.setComponentSize(width, height) JS method.
onSizeChanged: (data, size) {
//...
},
// This method is called when the in-app message content requests removal from the view hierarchy
// via the SRInApp.close() or SRInApp.closeAndTrigger() JS method.
onRemove: (data) {
//...
},
)
See the following code samples for the global listener/delegate:
Injector.setOnInlineInAppListener(new OnInlineInAppListener() {
// This method is called when the SDK creates a ready-to-display component
// after an event-based trigger for a placement with no existing host view.
// The view is registered under the placement key; call view.release() when you remove it.
@Override
public void onInlineInAppAvailable(InlineInAppView view, InlineInAppMessageData data) {
//...
}
// This method is called when a per-message context is needed for rendering.
// Background thread. Keep it fast.
@Override
public Map<String, Object> onContextFromAppRequired(InlineInAppMessageData data) {
return new HashMap<>();
}
// This method is called when the SRInApp.openUrl(url) method is used in an in-app message.
@Override
public void onOpenedUrl(InlineInAppMessageData data, String url) {
//...
}
// This method is called when the SRInApp.openDeeplink(url) method is used in an in-app message.
@Override
public void onOpenedDeepLink(InlineInAppMessageData data, String deepLink) {
//...
}
// This method is called when the SRInApp.handleCustomAction(name, params) method is used in an in-app message.
@Override
public void onCustomAction(InlineInAppMessageData data, String name, Map<String, Object> params) {
//...
}
// This method is called when the SRInApp.customMethod(name, params) method is used in an in-app message.
// Resolve the completion exactly once, with success or failure.
@Override
public void onCustomMethod(String name, HashMap<String, Object> params, InlineInAppMessageData data, InAppCustomMethodCompletion completion) {
//...
}
});
// Remove with Injector.removeInlineInAppListener(). Views rendered before the change keep the listener they were rendered with.
Injector.setOnInlineInAppListener(object : OnInlineInAppListener {
// This method is called when the SDK creates a ready-to-display component
// after an event-based trigger for a placement with no existing host view.
// The view is registered under the placement key; call view.release() when you remove it.
override fun onInlineInAppAvailable(view: InlineInAppView, data: InlineInAppMessageData) {
//...
}
// This method is called when a per-message context is needed for rendering.
// Background thread. Keep it fast.
override fun onContextFromAppRequired(data: InlineInAppMessageData): MutableMap<String, Any> {
return HashMap()
}
// This method is called when the SRInApp.openUrl(url) method is used in an in-app message.
override fun onOpenedUrl(data: InlineInAppMessageData, url: String) {
//...
}
// This method is called when the SRInApp.openDeeplink(url) method is used in an in-app message.
override fun onOpenedDeepLink(data: InlineInAppMessageData, deepLink: String) {
//...
}
// This method is called when the SRInApp.handleCustomAction(name, params) method is used in an in-app message.
override fun onCustomAction(data: InlineInAppMessageData, name: String, params: Map<String, Any>) {
//...
}
// This method is called when the SRInApp.customMethod(name, params) method is used in an in-app message.
// Resolve the completion exactly once, with success or failure.
override fun onCustomMethod(name: String, params: HashMap<String, Any>, data: InlineInAppMessageData, completion: InAppCustomMethodCompletion) {
//...
}
})
// Remove with Injector.removeInlineInAppListener(). Views rendered before the change keep the listener they were rendered with.
// MARK: - InjectorInlineInAppMessageDelegate
// Registered with Injector.setInlineInAppMessageDelegate(self) (weak reference); pass nil to remove.
// This method is called when the SDK creates a ready-to-display component
// after an event-based trigger for a placement with no existing host view.
// The SDK keeps only a weak reference to the view: retain it, or it is deallocated without warning.
func snr_inlineInAppMessageDidBecomeAvailable(view: InlineInAppView, data: InlineInAppMessageData) {
//...
}
// This method is called when a per-message context is needed for rendering.
// Background queue, synchronous: triggering waits until you return.
func snr_inlineInAppMessageContextIsNeeded(data: InlineInAppMessageData) -> [AnyHashable: Any]? {
return nil
}
// This method is called when the SRInApp.openUrl(url) method is used in an in-app message.
func snr_inlineInAppMessageHandledAction(data: InlineInAppMessageData, url: URL) {
//...
}
// This method is called when the SRInApp.openDeeplink(url) method is used in an in-app message.
func snr_inlineInAppMessageHandledAction(data: InlineInAppMessageData, deepLink: String) {
//...
}
// This method is called when the SRInApp.handleCustomAction(name, params) method is used in an in-app message.
func snr_inlineInAppMessageHandledCustomAction(data: InlineInAppMessageData, name: String, parameters: [AnyHashable: Any]) {
//...
}
// This method is called when the SRInApp.customMethod(name, params) method is used in an in-app message.
// Resolve the completion exactly once — with success or failure.
func snr_inlineInAppMessageHandledCustomMethod(data: InlineInAppMessageData, name: String, parameters: [AnyHashable: Any], completion: InAppCustomMethodCompletion) {
//...
}
Synerise.onReady(function() {
Synerise.Injector.setInlineInAppMessageListener({
// Required. Called when the SDK renders a campaign for a placement with no mounted component.
// view is a React element; mount it inside a sized container (see Event-based integration).
onInlineInAppAvailable: function(view, data) {
//...
},
// Called when the SRInApp.openUrl(url) method is used in an in-app message.
onOpenUrl: function(data, url) {
//...
},
// Called when the SRInApp.openDeeplink(url) method is used in an in-app message.
onDeepLink: function(data, deepLink) {
//...
},
// Called when the SRInApp.handleCustomAction(name, params) method is used in an in-app message.
onCustomAction: function(data, name, parameters) {
//...
},
// Called when the SRInApp.customMethod(name, params) method is used in an in-app message.
// Call completion.success(result) or completion.failure(message) exactly once.
onCustomMethod: function(data, name, parameters, completion) {
completion.success(null);
}
});
});
// There is no context callback in React Native. The SDK answers the native context request
// from Synerise.Injector.inAppContext[InAppContextKey.JinjavaContext].
Synerise.injector.inlineInAppMessageListener((listener) {
// This method is called when the SDK creates a ready-to-display component
// after an event-based trigger for a placement with no existing host view.
// `view` is a ready-to-place InlineInAppView widget for data.placementKey.
listener.onInlineInAppAvailable = (view, data) {
//...
};
// This method is called when the SRInApp.openUrl(url) method is used in an in-app message.
listener.onOpenUrl = (data, url) {
//...
};
// This method is called when the SRInApp.openDeeplink(url) method is used in an in-app message.
listener.onDeepLink = (data, deepLink) {
//...
};
// This method is called when the SRInApp.handleCustomAction(name, params) method is used in an in-app message.
listener.onCustomAction = (data, name, parameters) {
//...
};
// This method is called when the SRInApp.customMethod(name, params) method is used in an in-app message.
// Resolve the completion exactly once, with success or failure.
listener.onCustomMethod = (data, name, parameters, completion) {
completion.success(null);
};
});
// Remove with Synerise.injector.removeInlineInAppListener().
// There is no context callback in Flutter. The SDK answers the native context request
// from Synerise.injector.inAppContext[InAppContextKey.jinjavaContext].
Placement-based integration
In this approach, you create a component for a known placement, attach a per-component delegate/listener, and embed the view in your UI.
- Create the component with
Injector.createInlineInAppView(Android, iOS). In React Native, render<InlineInAppView placementKey="..." />; in Flutter, build anInlineInAppView(placementKey: '...')widget; steps 2 and 3 happen when the component mounts. On Android, you can also declare the view in XML withapp:placementKey; on iOS, useInlineInAppViewWrapperin storyboards and XIBs (InlineInAppViewitself cannot be created from a storyboard). - Set the per-component delegate/listener on the created view.
- Add the view to your view hierarchy.
- For campaigns with the automatic trigger, the SDK requests content as soon as the view is in the hierarchy. For on-demand campaigns, call
render()(ref.current.render()in React Native,_controller.render()in Flutter). Arender()issued while a request is in flight is queued.
NOTE: The component has no default size: the application must set its dimensions. The in-app message content is automatically pinned to all edges of the component. On Android and iOS, a view with zero size still renders, which lets you pre-render at 0 dp and expand later. In React Native, the style height must be set. In Flutter, give the widget bounded constraints (for example a SizedBox with a height).
InlineInAppView inlineView = Injector.createInlineInAppView(this, "home_top_banner");
inlineView.setOnInlineInAppViewListener(viewListener);
FrameLayout container = findViewById(R.id.banner_container);
container.addView(inlineView, new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT));
// On-demand campaigns only:
inlineView.render();
val inlineView = Injector.createInlineInAppView(this, "home_top_banner")
inlineView.setOnInlineInAppViewListener(viewListener)
val container: FrameLayout = findViewById(R.id.banner_container)
container.addView(inlineView, FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT))
// On-demand campaigns only:
inlineView.render()
guard let inlineView = Injector.createInlineInAppView(placementKey: "home_top_banner") else {
return
}
self.inlineView = inlineView // keep a strong reference; the SDK holds the view weakly
inlineView.delegate = self
inlineView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(inlineView)
NSLayoutConstraint.activate([
inlineView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
inlineView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
inlineView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
inlineView.heightAnchor.constraint(equalToConstant: 100)
])
// On-demand campaigns only:
inlineView.render()
const inlineRef = useRef(null);
<InlineInAppView
ref={inlineRef}
placementKey="home_top_banner"
style={{ width: '100%', height: 100 }}
onLoaded={(data) => {}}
onFailed={(data, error) => {}}
/>
// On-demand campaigns only:
inlineRef.current?.render();
// Also available on the ref: release(), isRendered(): Promise<boolean>, getData(): Promise<InlineInAppMessageData | null>
final InlineInAppViewController _controller = InlineInAppViewController();
SizedBox(
height: 100,
child: InlineInAppView(
placementKey: 'home_top_banner',
controller: _controller,
onLoaded: (data) {},
onFailed: (data, error) {},
),
)
// On-demand campaigns only:
_controller.render();
// Also available on the controller: release(), isRendered(): Future<bool>, getData(): Future<InlineInAppMessageData?>
Event-based integration
In this approach, a campaign is triggered by an event (sent through Tracker.send), not by embedding a view. The behavior depends on whether a component for the given placement is already in the view hierarchy:
- Component already embedded: the SDK updates the existing view's content. The per-component delegate/listener receives the
onUpdated/snr_inlineInAppViewDidUpdatecallback. The global listener/delegate is not called. On iOS, the view must already have a superview when the event fires; a created but not yet added view is skipped. - No existing component: the SDK creates a new
InlineInAppViewand delivers it through the global listener/delegate (onInlineInAppAvailable/snr_inlineInAppMessageDidBecomeAvailable). The application must embed the delivered view in the appropriate place. The view is delivered already rendered, soonLoaded/didLoaddoes not fire for it.
Who owns the delivered view:
- Android: the SDK registers it under the placement key. Call
release()when you remove it; otherwise it keeps receiving campaigns. - iOS: the SDK keeps only a weak reference. Store the view in a property, or it is deallocated as soon as the delegate method returns.
- React Native:
onInlineInAppAvailabledelivers a React element. Mount it inside a container with a height. Until it is mounted, the native view remains hidden; a newer campaign for the same placement replaces it. - Flutter:
onInlineInAppAvailabledelivers anInlineInAppViewwidget withadoptViewIdset. Mount it in a sized container. Removing the widget from the tree releases the native view automatically.
If no component is embedded and no global listener is registered, Android discards the campaign without logging. iOS renders a multi-placement Event campaign in its first placement key only.
@Override
public void onInlineInAppAvailable(InlineInAppView view, InlineInAppMessageData data) {
ViewGroup container = containerForPlacementKey(data.getPlacementKey());
if (container == null) {
view.release(); // not going to show it: unregister it
return;
}
view.setOnInlineInAppViewListener(this);
container.addView(view, new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT));
}
override fun onInlineInAppAvailable(view: InlineInAppView, data: InlineInAppMessageData) {
val container = containerForPlacementKey(data.placementKey)
if (container == null) {
view.release() // not going to show it: unregister it
return
}
view.setOnInlineInAppViewListener(this)
container.addView(view, FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT))
}
func snr_inlineInAppMessageDidBecomeAvailable(view: InlineInAppView, data: InlineInAppMessageData) {
guard let container = container(forPlacementKey: data.placementKey) else {
return
}
deliveredViews[data.placementKey] = view // strong reference; the SDK holds the view weakly
view.delegate = self
view.translatesAutoresizingMaskIntoConstraints = false
container.addSubview(view)
NSLayoutConstraint.activate([
view.topAnchor.constraint(equalTo: container.topAnchor),
view.bottomAnchor.constraint(equalTo: container.bottomAnchor),
view.leadingAnchor.constraint(equalTo: container.leadingAnchor),
view.trailingAnchor.constraint(equalTo: container.trailingAnchor)
])
}
const [banner, setBanner] = useState(null);
useEffect(() => {
Synerise.onReady(() => {
Synerise.Injector.setInlineInAppMessageListener({
onInlineInAppAvailable: (view, data) => {
if (data.placementKey === 'home_top_banner') {
setBanner(view);
}
},
});
});
}, []);
// The delivered element fills its parent; the parent must have a height.
<View style={{ height: 120 }}>{banner}</View>
// Trigger the campaign:
Synerise.Tracker.send(new CustomEvent('banner.requested', 'Banner requested'));
InlineInAppView? _banner;
Synerise.injector.inlineInAppMessageListener((listener) {
listener.onInlineInAppAvailable = (view, data) {
if (data.placementKey == 'home_top_banner') {
setState(() => _banner = view);
}
};
});
// Mount the delivered widget inside a sized container:
if (_banner != null)
SizedBox(height: 120, child: _banner!)
// Trigger the campaign:
Synerise.tracker.send(CustomEvent('banner.requested', 'Banner requested'));
Dynamic sizing
The in-app message content can read the current size of the hosting component by calling SRInApp.getComponentSize() from the JS interface. This returns an object with width and height properties in CSS pixels: density-independent pixels on Android, points on iOS. The arguments of setComponentSize use the same unit. Before the first layout, the result is {} on iOS 6.0.0 to 6.0.4 and zeros from 6.0.5.
The content can request a size change at any time by calling SRInApp.setComponentSize(width, height). The SDK communicates this request through the per-component delegate/listener. The application is responsible for updating the component's layout. Size updates pushed back to the message (SRInApp.onComponentSizeChanged on Android, SRInApp.onComponentSizeChange on iOS) are throttled to 500 ms.
The size object exposes the requested size in three units. Apply the one that matches your layout API:
| Platform | Physical pixels | Layout units | Fraction of the screen (0..1) | Apply to |
|---|---|---|---|---|
Android InlineInAppSize |
getWidthPx(), getHeightPx() |
getWidthDp(), getHeightDp() |
getWidthScreenRatio(), getHeightScreenRatio() |
LayoutParams.height = heightPx |
iOS InlineInAppSize |
widthPx, heightPx |
widthPt, heightPt |
widthScreenRatio, heightScreenRatio |
constraint constant = heightPt |
React Native InlineInAppSize |
widthPx, heightPx |
width, height |
widthScreenRatio, heightScreenRatio |
style={{ height: size.height }} |
Flutter InlineInAppSize |
widthPx, heightPx |
width, height |
widthScreenRatio, heightScreenRatio |
SizedBox(height: size.height) |
@Override
public void onSizeChanged(InlineInAppMessageData data, InlineInAppSize size) {
if (inlineView == null) {
return;
}
ViewGroup.LayoutParams layoutParams = inlineView.getLayoutParams();
layoutParams.height = size.getHeightPx();
ViewGroup parent = (ViewGroup) inlineView.getParent();
if (parent != null) {
TransitionManager.beginDelayedTransition(parent, new AutoTransition());
}
inlineView.setLayoutParams(layoutParams);
}
override fun onSizeChanged(data: InlineInAppMessageData, size: InlineInAppSize) {
val view = inlineView ?: return
val layoutParams = view.layoutParams
layoutParams.height = size.heightPx
(view.parent as? ViewGroup)?.let {
TransitionManager.beginDelayedTransition(it, AutoTransition())
}
view.layoutParams = layoutParams
}
func snr_inlineInAppViewChangeSizeIsNeeded(view: InlineInAppView, data: InlineInAppMessageData, size: InlineInAppSize) {
heightConstraint?.constant = CGFloat(size.heightPt)
UIView.animate(withDuration: 0.2) {
self.view.layoutIfNeeded()
}
}
const [height, setHeight] = useState(0);
<InlineInAppView
placementKey="home_top_banner"
style={{ width: '100%', height }}
// size.height is in layout units (dp on Android, points on iOS); do not use size.heightPx here.
onSizeChanged={(data, size) => setHeight(size.height)}
/>
double _height = 0;
SizedBox(
height: _height,
child: InlineInAppView(
placementKey: 'home_top_banner',
// size.height is in density-independent units (dp on Android, points on iOS)
onSizeChanged: (data, size) {
setState(() => _height = size.height);
},
),
)
Removing the view
When the in-app message content calls SRInApp.close() or SRInApp.closeAndTrigger(action, params, label) from the JS interface, the SDK sends an inApp.discard event (with closedBy=user). In the case of closeAndTrigger, the SDK additionally sends a custom event through Tracker. The SDK then requests removal through the per-component delegate/listener.
The onRemove callback is a request: the SDK does not remove the view itself, and the callback also fires when the application closes the campaign by calling Injector.closeInAppMessage(campaignHash)
- Android: after
SRInApp.close()the WebView andgetData()stay until you hide or release the view. Callrelease()when you remove a view while its Activity keeps running (for example, recycled list items or views you take out yourself). The SDK destroys the WebView itself when the Activity is destroyed.release()sends no event and does not callonRemove - iOS: there is no release method. Remove the view from its superview and drop your reference; the SDK holds it weakly.
- React Native: unmount the component. Unmounting releases the native view; mounting it again renders a fresh campaign and sends a new
inApp.showevent.ref.release()exists for releasing while staying mounted. - Flutter: remove the widget from the tree. Removing the widget disposes the platform view and releases the native view automatically.
controller.release()exists for tearing down while keeping the widget mounted.
@Override
public void onRemove(InlineInAppMessageData data) {
if (inlineView == null) {
return;
}
inlineView.release();
ViewGroup parent = (ViewGroup) inlineView.getParent();
if (parent != null) {
parent.removeView(inlineView);
}
inlineView = null;
}
override fun onRemove(data: InlineInAppMessageData) {
inlineView?.release()
(inlineView?.parent as? ViewGroup)?.removeView(inlineView)
inlineView = null
}
func snr_inlineInAppViewShouldBeRemoved(view: InlineInAppView, data: InlineInAppMessageData) {
heightConstraint?.constant = 0
view.removeFromSuperview()
if view === self.inlineView {
self.inlineView = nil
}
}
const [visible, setVisible] = useState(true);
{visible && (
<InlineInAppView
placementKey="home_top_banner"
style={{ width: '100%', height: 100 }}
onRemove={() => setVisible(false)} // unmounting releases the native view
/>
)}
bool _visible = true;
if (_visible)
SizedBox(
height: 100,
child: InlineInAppView(
placementKey: 'home_top_banner',
controller: _controller,
onRemove: (data) {
setState(() => _visible = false);
},
),
)
// Removing the widget from the tree releases the native view automatically.
Closing a message
Inline in-app messages can be closed from the message content or from the application.
From the message content:
SRInApp.close()sends aninApp.discardevent (closedBy=user) and triggers theonRemovecallbackSRInApp.closeAndTrigger(action, params, label)does the same and additionally sends a custom event through Tracker
From the application:
Call Injector.closeInAppMessage(campaignHash) to close a campaign programmatically. This generates an inApp.discard event with closedBy=application (Android) or closedBy=app (iOS) and triggers the onRemove callback. The campaign hash is available in the data delivered to the onLoaded callback.
On Android, closing an inline campaign closes every view showing it. On iOS, closing an inline campaign asks all views displaying it to be removed without sending an additional event.
See the method reference:
Failures
A render attempt ends in exactly one terminal callback: onLoaded, onUpdated, or onFailed. The onProcessingStarted callback is not called when no campaign matches the placement. A profile in a control group produces inApp.controlGroup and, on Android, no failure callback.
| Cause | Android onFailed error |
iOS NSError.code |
|---|---|---|
| No campaign for this placement, or none with the matching trigger type (or the campaign is capped) | No campaign found for placement key=... (data is null) |
4110, 4115 (4115 is not reported for automatic rendering) |
HTML did not load within renderingTimeout |
Html render timeout, plus inApp.renderFail |
3130 |
| Incoming campaign has equal or lower priority than the displayed one | lower-priority message | 4151 |
| Schedule, capping, control group, event mismatch | pipeline message | 4130, 4140, 4141/4142, 4120 |
| Template rendering error, WebView error, unknown | WebView or expression error text | 4150, 4160 |
Lists, rotation and declarative UI
- There is no Jetpack Compose or SwiftUI wrapper. Use
AndroidView/UIViewRepresentableand keep the view instance across recompositions. - Android destroys the WebView on a configuration change unless the Activity declares
configChangesin its manifest. After recreation, Automatic campaigns render again; On demand campaigns needrender()again. - Several views may share a placement key; each renders and reports
inApp.showon its own. - On Android,
isTestis alwaysfalsefor inline message data.