A starting point for new Roku applications.
- Preset globals
- Theme customization
- Node management + history + focus
- BaseScreen with lifecycle hooks (
onScreenVisible/onScreenHidden) - Centralized constants via
Const() - Leveled logging via
logError/logWarn/logInfo/logDebug - Translations
- Pop-up dialogs
- Testing (Not yet implemented)
-
A Roku device with a minimum OS version of 10.0**
** enforced at startup by /components/data/requirements.json; earlier OS versions may also fail to compile due to deprecated libraries
- This project uses the VSCode IDE along with the BrightScript Language Extension for packaging and deploying builds.
- Clone the repository to your preferred folder
- Rename the ".env-sample" file to ".env". This file is located at the root of the project folder.
- ROKU_HOST = your Roku device IP address
- ROKU_USERNAME = "rokudev" (default)
- ROKU_PASSWORD = the password you set when enabling developer mode
ZIP file is automatically generated in /out/roku-deploy.zip and sent to your Roku device:
- Windows, press F5
- MacOS, press Fn + F5
- The OUTPUT and DEBUG CONSOLE within VSCode will show the app activity as well as any print commands.
- Enabling (true) "showDeviceInfo" and/or "showAppInfo" at the top of /source/Main.brs will print information about the device to the console.
- If the app closes, the console/debug session will automatically close
Use the leveled helpers in /components/utils/Logger.brs instead of inline ? statements:
logError("something failed", "Source.brs")
logWarn("deeplink missing mediaType")
logInfo("App loaded")
logDebug("up key pressed", "LandingScreen.brs")
Output format: [LEVEL] message (Source.brs). The source argument is optional but useful for grep.
| Level | When printed |
|---|---|
logError |
Always (prefixed with a blank line) |
logWarn |
Always |
logInfo |
Only when m.global.devLogging is true |
logDebug |
Only when m.global.devLogging is true |
devLogging is declared at the top of /source/Main.brs alongside showDeviceInfo / showAppInfo / showBandwidth / showHttpErrors. Set it to false for a quieter production build.
Roku uses "m.global" to reference an object as seen in /source/Main.brs
- Global object values can be accessed from anywhere in the app.
m.global.uicould return a value of "HD" or "FHD". This is useful when picking remote image sizes (HD = 1280 x 720, FHD = 1920 x 1080).- 4 of the keys (
model,os,internet,hdcp) are used with /components/data/requirements.json to determine app eligibility at startup. See Requirements for more info. deeplinkis set from the launch args (contentId/mediaType) when present. SeegetDeepLinksin /source/Main.brs.devLoggingcontrols whetherlogInfo/logDebugoutput is printed. See Logging.
Roku's Scene node exposes a palette field for theming colors in child nodes; this template applies the selected theme onto the scene at startup.
- Themes are set from the top of /components/HomeScene.brs
"dark" and "red" is the default theme here if no 2nd argument is provided.
setTheme(true, { "type": "dark", "color": "red" })
- The palette colors, background colors and selectors can be customized in /components/data/themes.json
- themes are defined using the following format:
{
"dark": [
{
"color": {}
}
],
"light": [
{
"color": {}
}
]
}
- Read theme values via the
theme()accessor (from /components/utils/Themes.brs):
m.label.color = theme().colors.primaryTextColor ' palette color
m.list.focusBitmapUri = theme().selectorUri ' scene-level theme field
Palette colors live under
theme().colors; scene-level theme fields (backgroundColor,backgroundUri,selectorUri) are at the top level.
Magic strings and numbers (key codes, file paths, registry section, beacon names, dialog typography) live in /components/utils/Constants.brs as an AA returned by Const():
key = Const().key.back ' "back"
ReadAsciiFile(Const().path.messages) ' "pkg:/components/data/messages.json"
node.signalBeacon(Const().beacon.launchComplete)
fontSize = Const().dialog.messageFontSize ' 32
Add new groups to Const() rather than introducing new literals in call sites. See /components/utils/Constants.brs for the full table.
Dev-time logging via logDebug (suppressed unless m.global.devLogging is true) — call from any screen handler:
showAllScreens()— every direct child of HomeScene with its idgetHistory()— current screen-stack contents (only populated on HomeScene)showFocus()— root-to-leaf focus chainshowTheme()— palette colors + scene-level theme fields
Any screen that extends BaseScreen must define its own init() and call baseScreenInit() as the first line — that hides the screen until shown and wires the visibility observer. After that, set up node refs and field observers as usual:
sub init()
baseScreenInit()
m.myList = m.top.findNode("myList")
m.myList.observeField("itemSelected", "onItemSelected")
end sub
Two optional hooks fire when the screen's visible field changes — override either by redeclaring it in the screen's .brs:
sub onScreenVisible()
' run every time the screen becomes visible — fetch data, set focus, etc.
end sub
sub onScreenHidden()
' run every time the screen is hidden — pause animations, cancel requests, etc.
end sub
Each hook is a no-op by default. Implementing one in a subclass overrides the parent's no-op since SceneGraph loads child scripts after the parent's. See /components/screens/landing/LandingScreen.brs for a working example.
BaseScreen also declares a focusedNode field on its interface, used by setFocus(node, saveFocus) to remember which child node was last focused — see Setting screen focus below.
Screens are identified by their component name (the XML
nameattribute, also returned bysubType()). Adding a screen whose subtype already exists logs a console error and skips the add — only one instance of a given screen type lives in the scene at a time.
addScreen("screenName")
Additional arguments can be used as follows:
addScreen(
screenName ' the name of the node/XML file (also the subtype)
showScreen ' sets the node visibility (true by default)
hidePrevScreen ' hides the previous/current screen (true by default)
trackInHistory ' adds the node to the history stack (true by default)
)
Looked up by component subtype, walking the immediate children of the scene. Returns invalid (and logs a warning) if not found.
getScreen("screenName")
Additional arguments can be used as follows:
getScreen(
screenName ' the component name of the existing screen
showScreen ' set the node visibility to true (false by default)
)
Pass the child node directly, or pass the component name of the screen to look up and remove.
removeScreen(node or "screenName")
Additional arguments can be used as follows:
removeScreen(
screen ' the child node itself or the component name of the screen
showPrevScreen ' shows the previous screen (true by default)
untrackHistory ' removes the node from the history stack (true by default)
)
Pass the node directly, or pass the component name of the screen to focus.
setFocus(node or "screenName")
Additional arguments can be used as follows:
setFocus(
node ' the node itself or the component name of the screen
saveFocus ' writes the focused node to m.top's focusedNode field (true by default)
)
focusedNodeis declared on theBaseScreeninterface. When a BaseScreen-derived screen becomes visible again after being hidden,removeFromStackreads this field and re-focuses the saved node.
Screens extending BaseScreen automatically inherit every utility script (Screens.brs, History.brs, Themes.brs, Messages.brs, Requirements.brs, General.brs, Beacons.brs, Constants.brs, Logger.brs, Debug.brs) via the parent component's <script> tags — you don't need to re-include them. The only place to register new utilities is in /components/screens/base/BaseScreen.xml (and /components/HomeScene.xml if HomeScene also needs the utility).
See /components/screens/landing/LandingScreen.xml for a working example.
- The language translation folders are located at /locale
- Each folder is named according to the locale ID (see Roku localization docs for list of ID's)
- The files in each folder use the XLIFF format
- additional folders and translations can be added by following the format above
tr("string")is a Roku built-in that resolves the string against the active locale's XLIFF entries; it returns the original string when no translation exists, so it's safe to use everywhere. Plain string literals work too if you don't need translation.
Show a dialog from a /components/data/messages.json entry by key:
showMessage("message")
Or pass a dialog config object directly to showDialog(config) (see /components/utils/Messages.brs) when the dialog isn't backed by a JSON entry.
Entries in messages.json follow this schema:
{
"message": {
"title": "title at top of dialog window",
"message": "text inside body of dialog window",
"help": [
"items shown below the message body",
"help is optional and can be an empty array if not needed"
],
"buttons": [
{ "label": "OKAY", "exitApp": true },
{ "label": "CANCEL" }
],
"allowBack": set to true to allow pressing the back button on the remote to exit the dialog window
}
}
Each button is an object with a required label and two optional fields:
exitApp: true— setsscene().exitAppwhen pressed, breaking the main loop in /source/Main.brsonPress: { "node": nodeRef, "func": "funcName" }— invokesnodeRef.callFunc(funcName, { label, index })when pressed
Buttons without exitApp dismiss the dialog after firing their onPress (if any). See onButtonSelected in /components/screens/dialogs/DialogModal.brs.
- Examples and other Roku libraries
- Help & Support
- Docs & Tutorials:
