Rovenue SDK
FunnelsNative

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")
    }
)
_ = handlers

Pass 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.