Observer Callback Models
Firmware v7.5 introduces a fundamental change in the observer callback model changing from a queued or deferred model to a more intuitive and expected recursive callback model.
Queued Callback Model
In firmware releases prior to v7.5, callbacks operated in a queued manner.
In the nested functions example above, the queued callback model would have the ordering of the print statements as:
If your channel was available prior to firmware v7.5 and operated with the queued callback model, you can set the following attribute in the manifest to continue using the old behavior:
Recursive Callback Model
With the recursive callback model, the expected output is now more intuitive and the order of the print statements would be:
With firmware version 7.5 and greater, the
rsg_version entry in your channel's manifest will default to 1.1. To check and test different SceneGraph versions without refactoring your channel, see the guide on Debugging.
There are three types of events that can occur in a Scene Graph application that you can use to control the behavior of an application component.
- User remote control key presses
- Component and node field value changes, such as state changes of a previously-launched component, and node actions
- Functional Fields
Handling Remote Control Key Presses
You can handle remote control key events by writing an
onKeyEvent() function in the <script> element of the component you want to handle the key press event. When the component, or its children, have remote control focus, the
onKeyEvent() function is called whenever an unhandled key event bubbles up the focus chain to the component.
Using the onKeyEvent() Function
onKeyEvent() function takes two parameters,
press parameter is a Boolean value that is true if the key was pressed, and false if the key was released. The
key parameter of the
onKeyEvent() function contains a string, which is case-sensitive, that identifies the key that was pressed. The
key strings supported by the
onKeyEvent() function, and the corresponding remote key, are as follows:
|back||Back||left-pointing arrow at top of remote|
|up||Up||up-pointing caret of remote directional pad|
|down||Down||down-pointing caret of remote directional pad|
|left||Left||left-pointing caret of remote directional pad|
|right||Right||right-pointing caret of remote directional pad|
|OK||OK||key usually labeled OK near or in the center of remote directional pad|
|replay||Replay||key usually labeled with a circular-pointing arrow|
|play||Play/Stop||key usually labeled with a right-pointing triangle and two bars|
|rewind||Rewind||key usually labeled with two left-pointing triangles|
|fastforward||Fast Forward||key usually labeled with two right-pointing triangles|
|options||Options||key labeled with an asterisk|
onKeyEvent() function must return true if the component handled the event, or false if it did not handle the event. Returning false allows the event to continue bubbling up the focus chain (see Remote Control Events), so that ancestors of the component can handle the event.
There are one or more keys on any Roku remote control which are not handled by the
onKeyEvent() function (or any Roku application event handler), such as the Home key. Presses of these keys are handled by the global Roku firmware event handler in a default manner that cannot be modified by application code. Also note that several node classes handle certain remote control key events automatically, so
onKeyEvent() is not required to handle those events, and should not be used for those events in those nodes. As an example of node classes that automatically handle certain remote control key events, grid node classes such as PosterGrid automatically handle Up, Down, Right, and Left key presses when the poster grid has focus. Typically, you should use the ifSGNodeField
observeField() method to handle changes in the subject node fields caused by automatic key event handling of the node.
onKeyEvent() example handles supported remote control key presses other than the Back key by displaying a warning message until the OK key is pressed.
Handling Node Field Value Changes
There are two ifSGNodeField methods that allow you to create (and remove) observers that continuously monitor any field value of any roSGNode object, including the interface fields you have created for custom Scene Graph components:
Using the ifSGNodeField observeField() and unobserveField() Methods
observeField() is an overloaded method with two versions, useful for different purposes. The first allows you to trigger a specified callback function in response to any change in the value of the observed node field. For example, to set up an observer of a Timer node
fire field that calls a
handleexampletimerfire() event handler function that you write:
Once this observer is set up, the component will continuously monitor the
exampletimer node object
fire field for the remaining existence of the component or node, or until
unobserveField() is called (perhaps as part of the event handler function itself):
The event handler function you write must be included in the component <script> element, and manipulate objects within the scope of the component.
alwaysNotifyattribute set to true.
Here is an example of a field observer and the associated event handler function:
The second version of
observeField() lets you specify a message port to notify when the observed field changes:
This second case is used when you want a field change to trigger an event in a BrightScript message loop. This is useful when using BrightScript components (such as roChannelStore) which can only be instantiated in the main BrightScript thread or a Task node thread. In this case, when the observed field changes, an roSGNodeEvent is sent to the port passed to the
observeField() call. The event
getData() functions can be called to determine the specific node field that changed, and the new value of the field, respectively.
Setting the value of a field triggers any field observer functions that may be associated with the field as the result of an observeField() call. These observer functions may set other component fields, triggering calls to their observer functions. Such a situation triggers what is called an event cascade. Simply put, this is the chain of field setting and observer function calls that result from setting a single field of a node.
For example, if the width field of a Rectangle that contains a Label is set, an observer of that width field might set the width field of the Label. The width field of the Label might have an observer function that sets the Label’s wrap field. The chain of field settings and observer callback functions that result from setting a field (in this case, the Rectangle’s width field) is an event cascade.
With the release of firmware v7.5, nested observer callbacks are no longer deferred. Observer callbacks now happen recursively. See the Queued Callback Model section above for details.
Handling Component <interface> Field Value Changes
Any <field> element defined in a component <interface> element can have an observer attached by setting the value of the optional
onChange attribute. Set the
onChange attribute to the callback function name that will handle the component field value change.
Note that the <field> element also includes a related optional attribute,
alwaysNotify. You can set this attribute to true to have the callback function triggered every time the component field value is set, even if the value itself does not change. To have the callback function triggered only when the component field value changes, set the
alwaysNotify attribute to false.
Firmware v7.5 introduces the concept of a functional field. In addition to the field observer model, functions can now be called procedurally.
To use a functional field, define the function within an interface:
Next, define what the function does in BrightScript:
callFunc() is a synchronized interface on roSGNode. It will always execute in the component's owning ScriptEngine and thread (by rendezvous if necessary), and it will always use the m and m.top of the owning component. Any context from the caller can be passed to the function via an associative array, which is fully converted/cloned on call. The result is also fully converted/cloned on return as well.
To call the function, use the
callFunc field. Parameters can only be passed via one object which can be an associative array argument or an array with arbitrary convertible contents. A return value, if any, can be an object that is similarly arbitrary.
Your function must determine how to interpret the parameters.