Roku
Configuration guide for the Recurly Engage Roku SDK, which enables native prompt display and usage tracking in your Roku applications.
Key benefits
Key details
Install the SDK
Download the latest Roku SDK (v1.0.49) with Roku Pay support or without Roku Pay support. A demo app with an example integration is available by request.
To build a project using the Recurly Engage SDK for Roku, your project must have been built with the SceneGraph SDK. Unzip the SDK into the app components directory.
Initialize the SDK
< PromotionManager id="promoMgr" />[TODO: Dev/PO review — possible issue: the initPromotion call below uses the key annonymousUserId (double n). Left verbatim.]
sub init()
' other app initialization code here
' optionally specify cta-button and timer countdown fonts
ctaF = CreateObject("roSGNode", "Font")
ctaF.uri = "pkg:/fonts/Roboto-Regular.ttf"
ctaF.size = 16
timeoutF = CreateObject("roSGNode", "Font")
timeoutF.uri = "pkg:/fonts/Roboto-Regular.ttf"
timeoutF.size = 16
m.promoMgr = m.top.GetScene().findNode("promoMgr") ' or m.top.findNode("promoMgr")
print m.promoMgr.callFunc("getVersion") ' lookup current SDK version
m.promoMgr.observeField("result", "onInitialized")
' appId argument is required, all others are optional
m.promoMgr.callFunc("initPromotion", {appId: "[YOUR APP ID]", userId: "[USER ID]", annonymousUserId: "[ANON USER ID]", ctaFont: ctaF, timeoutFont: timeoutF})
end sub
sub onInitialized()
m.promoMgr.unobserveField("result")
m.sceneStack = m.top.findNode("sceneStack")
scene = createObject("RoSGNode", "[YOUR FIRST SCREEN OF THE APP]")
m.sceneStack.appendChild(scene)
end subIt may take a few seconds after app start for the SDK initialization to complete. After that, prompts are available to present to the user.
Set the user ID
You can change the userId after the SDK has been initialized. The function returns instantly, but all prompts relevant to the updated userId may take a few seconds to be ready.
m.promoMgr.callFunc("setUserId", {userId: "[new user id]"})Set the anonymous user ID
You can update the anonymousUserId after the SDK has been initialized. If you don't set it, the SDK assigns the user a randomly generated universally unique identifier (UUID).
m.promoMgr.callFunc("setAnonymousUserId", {userId: "[new anon user id]"})Set privacy consent categories
You can configure prompts in Pulse to require one or more privacy consent categories. To restrict which prompts are eligible to be shown, specify the categories the user has consented to (for example, after they interact with a cookie or privacy consent banner). A prompt is only considered eligible when its configured consent categories are an exact match (the same set) of the categories you specify here.
If setPrivacyConsentCategories is never called, consent filtering is disabled and all prompts remain eligible regardless of their configured consent categories.
' Supported category values, see `PrivacyConsentCategory()` in the SDK `consts.brs` file:
' m.strictlyNecessary = "strictly_necessary"
' m.performance = "performance"
' m.functional = "functional"
' m.targeting = "targeting"
m.promoMgr.callFunc("setPrivacyConsentCategories", {categories: ["strictly_necessary", "performance"]})You can retrieve the currently set categories at any time:
categories = m.promoMgr.callFunc("getPrivacyConsentCategories")Supported prompt types
| Prompt type | Enum value |
| Modal | 2 |
| Horizontal | 5 |
| Video | 6 |
| Interstitial | 10 |
| Bottom banner | 13 |
Trigger a modal via screen name
You can use the SDK to display a modal on a specified screen. If the prompt also requires a button click, the trigger doesn't occur until the associated onButtonClicked function is called.
Add the following lines in the screen init function:
[TODO: Dev/PO review — possible issue: the example below uses // comments and the typo "registed", and BrightScript comments normally start with an apostrophe. Left verbatim.]
sub init()
...
m.promoMgr = m.top.GetScene().findNode("promoMgr")
// Make sure a callback function is registed (and registed for only once) here to receive any status from m.promoMgr
// A sample onPromotionEvent is provided in the next section
m.promoMgr.observeField("result", "onPromotionEvent")
m.promoMgr.callFunc("onScreenChanged", {root: m.viewRoot, screenName: "ViewController" })
...
end subMake sure you add an event listener before calling any function on m.promoMgr, so that you can observe the result from a screen change, button click, popup, or inline item:
m.promoMgr.observeField("result", "onPromotionEvent")Trigger a modal via button click
The sample code below demonstrates:
- Tracking a button click event
- Displaying a modal, if one applies to the button
A previous onScreenChanged call is required if the prompt trigger is configured to be invoked only when the button click occurs on a specified screen name.
sub onButtonClicked()
m.promoMgr.callFunc("onButtonClicked", {root: m.viewRoot 'the root component of the screen, id: "[Optional button ID]"})
end sub
'Note: If the prompt is displayed and dismissed, the result will be passed back to:
sub onModalDismissed()
if m.promoMgr.result.value = 101 'button1
dialog = createObject("roSGNode", "Dialog")
dialog.title = "Thank you"
dialog.optionsDialog = true
dialog.message = "Thanks for accepting"
m.top.dialog = dialog
end if
end sub
' Note: The full set of status codes can be found in the SDK `const.brs` file:
' Success codes
m.ok = 1
' Error codes
m.error = -100
m.notApplicable = -101
m.disabled = -102
m.suppressed = -103
' Interactions
m.impression = 100
m.button1 = 101
m.button2 = 102
m.button3 = 103
m.dismissed = 110
m.timerExpired = 111
m.holdout = 120
Show a modal via manual trigger
You can trigger a prompt manually if triggering by screen name or button click isn't what you want.
prompt = m.promoMgr.callFunc("getPrompt", {pathId: "myPathId"})
m.promoMgr.callFunc("showPrompt", {root: m.viewRoot, prompt: prompt})Show an inline prompt
An eligible inline prompt can be rendered within a SceneGraph node. We recommend defining a Rectangle or Poster node as a container for the inline prompt. The scale argument determines how the inline prompt scales to fit within the allocated space of the specified node.
' -- Scenegraph component file (.xml) --
<Rectangle id="myBanner" width="1920" height="420" />
' -- Brightscript file (.brs) --
' showInline Args:
' root: parent node
' type: Zone ID
' scale: "scaleToFit", "scaleToFill", "scaleToZoom", "noScale"
m.inline = m.promoMgr.callFunc("showInline", {root: m.myBanner, type: "myZoneId", scale: "scaleToFill"
})
' Observe Prompt Interactions
if m.inline <> invalid
m.inline.observeField("result", "onInlineResult")
end if
Retrieve inline prompts
For custom rendering, the SDK provides a method to retrieve the inline prompts in the specified Zone ID that are eligible for the current userId. You can access the properties of the inline prompts to render them in the appropriate locations within the app.
inlineItems = m.promoMgr.callFunc("getInlines", {type: "myZoneId"})The following example code accesses attributes of the prompt for rendering within a new child node. You can find the full list of attributes here.
featured = createObject("RoSGNode", "ContentNode")
di = CreateObject("roDeviceInfo")
displaySize = di.GetDisplaySize()
for ii = 0 To inlineItems.count() - 1
item = featured.createChild("ContentNode")
inlineItem = inlineItems[ii]
heightSuffix = ""
if displaySize.h = 480
heightSuffix = "&screen_size=480"
else if displaySize.h = 720
heightSuffix = "&screen_size=720"
end if
item.HDPOSTERURL = inlineItem.actions.rf_settings_bg_image_roku_os_tv_composite + heightSuffix
end for
featured.title = "Featured"
contentNode.insertChild(featured, 2)Prompt interactions for inline prompts must be reported within your application code.
' Report impression when inline prompt is viewed
promoMgr.onInlineViewed(inlineItem)
' Report click
promoMgr.onInlineClicked(inlineItem)
' Report dismiss
promoMgr.onInlineDismissed(inlineItem)Respond to prompt interactions
The SDK returns a PromptResult object upon any prompt interaction performed by the user.
The PromptResult schema includes the following properties:
code: Interaction code (for example, 100 for impression, 101 for button1 click)meta: Device metadata specified within the promptpromptMetapromptNamepromptIDpromptTypepromptVariationNamepromptVariationIDpromptExperimentNamepromptExperimentIDbuttonLabel
' Observe Prompt Interactions
m.promoMgr.observeField("result", "onPromptResult")
' Perform actions on resulting interaction
sub onPromptResult()
' Call custom sendAnalytics() function
sendAnalytics(m.promoMgr.result)
' Kick off Roku Pay flow for specified SKU if specified
if m.promoMgr.result.roku <> invalid and m.promoMgr.result.roku <> ""
m.promoMgr.callFunc("purchaseIap", {sku: m.promoMgr.result.roku, qty: 1})
else
if m.modal.visible
m.detail.setFocus(true)
else
m.home.setFocus(true)
end if
end if
end sub
Send a usage tracking event
Your app can report custom tracker events through the SDK. If you configure a tracker in Pulse, you can use these custom events to target prompts at specific sets of users. Create the custom tracker in Pulse first, so you can retrieve the customFieldId value.
m.promoMgr.callFunc("customTrack", {customFieldId: "my-usage-event"})Deep link to a media asset
Invoke the Roku app deep linking functionality by specifying the mediaType and contentId for the prompt in Pulse. When the user selects the call-to-action (CTA) on the prompt, you can use these parameters to send the user to a specific media asset within the app. The following example shows how to invoke deep linking after the user selects the CTA.
sub onPromotionEvent()
if m.promoMgr.result.value = 0 // m.accepted from `const.brs` file
deeplink = m.promoMgr.result.extra.deeplink
[your deeplink handler function](deeplink)
end if
end subAccess device metadata
You can add device key-value pair metadata to a prompt in Pulse. The app receives these values as shown in the examples below. You can use them to perform an action other than the typical media asset deep link, like sending the user to a registration screen.
Popup
When the user dismisses a popup with an Accept, Decline, or Timeout action, the custom metadata is saved in the extra.meta field.
sub onPromotionEvent()
metadata = m.promoMgr.result.extra.meta
end subInline item
When the onInlineClicked API is invoked with the currently selected inline item:
sub onPromotionEvent()
metadata = m.promoMgr.result.extra.meta
end subPrompt type enum values
| Enum prompt type | Integer value |
| all | -1 |
| invisible | 1 |
| modal | 2 |
| horizontal | 5 |
| video | 6 |
| interstitial | 10 |
| bottom banner | 13 |
Disable the SDK
In some cases, you may need to temporarily disable the SDK for the current session. When the SDK is disabled, popups aren't triggered, and API communication between the SDK and Recurly Engage servers is paused.
// To disable
m.promoMgr.callFunc("enablePromotion", {enabled: false})
// To re-enable
m.promoMgr.callFunc("enablePromotion", {enabled: true})Debug view
The SDK provides a debug view modal. Use the onscreen keyboard to reset all prompts for the current user, set a new userId, or set the active privacy consent categories (a comma-separated list, for example, strictly_necessary,performance; leave it blank to disable consent filtering).
To trigger the debug view for a specific screen, add the DebugView component and connect it to a local variable on the screen:
<DebugView id="debugView" />
m.debugView = m.top.findNode("debugView")
In the screen's onKeyEvent function:
m.debugView.callFunc("onKeyDetection", {key: key, screen: m.top})When the * key on the remote control is pressed, the debug view is displayed.
Claude skill
A Claude skill for this SDK is available for download at SKILL.md. This skill enables Claude to assist with SDK integration, prompt configuration, and event handling in your Roku application.
Updated 5 days ago