Show a Funnel
Resolve a placement to a native funnel and host it — getFunnel into RovenueFunnelView on every SDK.
Install the SDK first — iOS, Android, React Native, Flutter — then resolve the placement and host the view. There is no web tab: sdk-web draws no funnel; web funnels are hosted at a URL instead (see Hosting).
On Swift and Kotlin the call is getFunnel and the session is already open
when it returns — opened in core, once
(packages/sdk-swift/Sources/Rovenue/Rovenue.swift:717-729,
packages/sdk-kotlin/src/main/kotlin/dev/rovenue/sdk/Rovenue.kt:631-642).
nil/null means the placement holds no funnel (retired, target: none,
unknown identifier) — not an error. On React Native and
Flutter there is deliberately no JS/Dart getFunnel — nothing about a
funnel crosses the bridge, so the native side resolves the placement itself
and the widget only hosts the native view
(packages/sdk-rn/src/index.ts:151,
packages/sdk-flutter/rovenue_flutter/lib/src/funnel_view.dart:10-14).
if let funnel = try await Rovenue.shared.getFunnel(placementId: "onboarding", locale: "en") {
let driver = FunnelDriver(session: funnel.session)
let view = RovenueFunnelView(driver: driver)
_ = view
}Hand the session to a FunnelDriver, and the driver to
RovenueFunnelView (packages/sdk-swift/Sources/Rovenue/FunnelUI/RovenueFunnelView.swift:244-250).
When the host dismisses its container it calls driver.close(): that emits
funnel_dismiss once and stops the clock, and deliberately does not fire
onFinished — closing is the host's own decision, not an event to report
back to it
(packages/sdk-swift/Sources/Rovenue/FunnelUI/RovenueFunnelView.swift:175-181).
Persist driver.stateJson() to resume the same presentation next launch
(see Purchases & effects).
let handlers = FunnelHostHandlers(
onCustom: { actionId, paramsJson in
print(actionId)
print(paramsJson)
},
onPurchase: { packageId in
print(packageId)
},
onRestore: {
print("restore requested")
}
)
_ = handlersPass the handlers into the driver: a custom action arrives as
onCustom with its id and params JSON, a purchase step as onPurchase
with the package id, a restore step as onRestore
(packages/sdk-swift/Sources/Rovenue/FunnelUI/RovenueFunnelView.swift:44-57,
packages/sdk-swift/Sources/Rovenue/FunnelUI/RovenueFunnelView.swift:215-234).
An effect with no handler is dropped, never fatal.
import dev.rovenue.sdk.funnelui.FunnelDriver
import dev.rovenue.sdk.funnelui.UniffiFunnelSession
val funnel = Rovenue.shared.getFunnel(placementId = "onboarding", locale = "en")
if (funnel != null) {
val driver = FunnelDriver(UniffiFunnelSession(funnel.session))
println(driver.stateJson())
}Rovenue.shared is the configured instance
(packages/sdk-kotlin/src/main/kotlin/dev/rovenue/sdk/Rovenue.kt:206-208).
Wrap the session, drive it with a FunnelDriver, and attach the driver to
the view with bind(driver) — the view redraws on every screen change,
including the one it already holds
(packages/sdk-kotlin/src/main/kotlin/dev/rovenue/sdk/funnelui/RovenueFunnelView.kt:56-60).
The resolved funnel is ready to drive; hand it to the view through the
driver
(packages/sdk-kotlin/src/main/kotlin/dev/rovenue/sdk/funnelui/ResolvedFunnel.kt:21).
When the host closes the view it calls driver.close(), which emits
funnel_dismiss once and does not fire onFinished
(packages/sdk-kotlin/src/main/kotlin/dev/rovenue/sdk/funnelui/FunnelDriver.kt:157-161).
import dev.rovenue.sdk.funnelui.FunnelHostHandlers
val handlers = FunnelHostHandlers(
onCustom = { actionId, paramsJson -> println("$actionId $paramsJson") },
onPurchase = { packageId -> println(packageId) },
onRestore = { println("restore requested") },
)Every handler is optional
(packages/sdk-kotlin/src/main/kotlin/dev/rovenue/sdk/funnelui/FunnelDriver.kt:62-77):
onCustom carries the action id and its params as JSON, onPurchase the
package id to buy, onRestore the restore request
(packages/sdk-kotlin/src/main/kotlin/dev/rovenue/sdk/funnelui/FunnelDriver.kt:198-209).
The widget hosts the platform's native funnel renderer for the placement —
the native side resolves and draws, so there is no JS getFunnel and no
object to pass in
(packages/sdk-rn/src/funnel-view/RovenueFunnelView.tsx:60-66).
import { RovenueFunnelView } from "@rovenue/react-native-sdk";
function OnboardingFunnel() {
return (
<RovenueFunnelView
placementIdentifier="onboarding"
locale="en"
onFinished={() => undefined}
/>
);
}Size it with your own flex container — the funnel fills whatever frame the
host gives it. onFinished fires at most once, when the funnel ends or is
dismissed; remove the view there
(packages/sdk-rn/src/funnel-view/RovenueFunnelView.tsx:19-42). Omit
onUrl to leave open_url actions inert — the funnel never navigates itself.
A legal block's terms link goes to onUrl too; with no handler it opens only
an http/https address, in the system browser, and any other scheme does
nothing — the same rule on iOS, Android and the web. Whether a handler exists
is decided when the funnel resolves: removing onUrl later stops URLs
reaching your code, and adding one later takes effect on the next resolve.
import { RovenueFunnelView } from "@rovenue/react-native-sdk";
function OnboardingFunnelActions() {
return (
<RovenueFunnelView
placementIdentifier="onboarding"
onCustomAction={(action) => console.log(action.actionId, action.params)}
onPurchase={(packageId) => console.log(packageId)}
onRestore={() => console.log("restore requested")}
/>
);
}A custom action arrives parsed as FunnelCustomAction — actionId plus an
already-decoded params map; malformed params JSON yields {}, never a
throw inside the native callback
(packages/sdk-rn/src/funnel-view/RovenueFunnelView.tsx:7-17,
packages/sdk-rn/src/funnel-view/RovenueFunnelView.tsx:44-52). onPurchase
hands you the package id to buy with Rovenue.purchase; onRestore asks
for restorePurchases.
The widget hosts the native funnel renderer — SwiftUI's
RovenueFunnelView on iOS, dev.rovenue.sdk.funnelui.RovenueFunnelView on
Android — as a platform view
(packages/sdk-flutter/rovenue_flutter/lib/src/funnel_view.dart:1-7).
RovenueFunnelView buildOnboardingFunnel() {
return const RovenueFunnelView(placementIdentifier: 'onboarding');
}The funnel fills whatever space the parent gives it; size it with a
SizedBox/Expanded. onFinished fires at most once, when the funnel
ends or is dismissed
(packages/sdk-flutter/rovenue_flutter/lib/src/funnel_view.dart:81).
Changing placementIdentifier on a live instance re-resolves and restarts
the funnel at its first screen
(packages/sdk-flutter/rovenue_flutter/lib/src/funnel_view.dart:46-61).
RovenueFunnelView buildOnboardingFunnelActions(void Function(RovenueFunnelAction action) onAction) {
return RovenueFunnelView(
placementIdentifier: 'onboarding',
onCustomAction: onAction,
onPurchase: (packageId) {},
onRestore: () {},
);
}RovenueFunnelAction carries the app-defined action id and its
already-decoded params
(packages/sdk-flutter/rovenue_flutter/lib/src/funnel_view.dart:30-41).
onPurchase hands you the package identifier to buy with
Rovenue.instance.purchase; onRestore asks for restores
(packages/sdk-flutter/rovenue_flutter/lib/src/funnel_view.dart:91-97).
Omit onUrl to leave open_url actions inert. A legal block's terms link
goes to onUrl too; with no handler it opens only an http/https address in
the system browser, and any other scheme does nothing
(packages/sdk-flutter/rovenue_flutter/lib/src/funnel_view.dart:84-86).
Presence is sent as hasUrlHandler and decided when the funnel resolves:
removing onUrl later stops URLs reaching your code, and adding one later
takes effect on the next resolve.
What happens inside the session — effects, purchases, restore and resume — is next: Purchases & effects.