Accessing a global object inside a SceneGraph Component

Hello,

I’m fairly new to SceneGraph. I have a library of methods, basically utility functions which I’ve created that I’d like to use in my SceneGraph components. Things like modifying arrays, make calls to retrieve XML from an HTTP service, and parse the XML.

After reading the documentation, it sounded like I should be using ‘screen.getGlobalNode()’ to assign my object of functions into a global scope that would become accessible to the SceneGraph component. However, when I take a look at the object, I’m attempting to access, all of the functions are defined as ‘invalid’ and attempting to access them will crash the app:

<Component: roAssociativeArray> =
{
addfavorite: invalid
analytics_parameters: “channel=23&sessionKey={0}&upc={1}&chapterId={2}&percentPlayed={3}&stopped=”
bcc: <Component: roAssociativeArray>
calljsonservice_: invalid
callxmlservice_: invalid
checkkey: invalid
checklogin: invalid
checksubscription: invalid
context: <Component: roAssociativeArray>
getbookmarks: invalid
getfavorites: invalid
getkey: invalid
getloginkey: invalid
getloginkeybyuser: invalid
getsessionkey: invalid
login_key: “my_login_key”
loginmode: “”
logout: invalid
registeruser: invalid
session_key: “my_session_key”
trackevent: invalid
validateemail: invalid
validatepassword: invalid
validatereceipt: invalid
validatetransaction: invalid
}

Any variables set on the object are accessible as I would expect, but the functions themselves are not. (Basically, anything listed as “invalid” up there.)

Is there a way to store a library of globally accessible utility functions in one place that can be accessed by any SceneGraph Components?

Thank you,

-Mikey

rather than trying to include them as methods on the global noid, I believe you can (should?) just define global functions in source and reference them from anywhere.
For example, if you just include a file in your source folder called utils.brs and in there you can have a function;

Function myUtils()
    this = {
        someFunction : _someFunction
        getSomething : _getSomething
    }
    return this
End Function

Within the same file, define the functions ‘_someFunction’ and ‘_getSomething’
Now you can instantiate myUtils from anywhere ( util = myUtils() ) and pass in variables to the defined methods ( result = util.getSomething(somevars) ).

Thanks for the follow-up. I think I’ve tried what you are suggesting, but seem to get the same output. Here is some code to go along with my issue

  1. Utility Class - util.brs

I’ve created a test util.brs as you’ve suggestion with two sample functions I’d like to use across the app:

function util()
    obj = {
        printTest: _printTest
        getSomething: _getSomething
    }
    
    return obj
end function

function _printTest()
    print "_printTest()"
end function

function _getSomething()
    print "_getSomething()"
end function
  1. My SceneGraph Screen - SubscribeScreen.brs

I’m invoking ‘util()’ in the roSGScreen itself, because it seems that if I attempt to call it in the ‘RowListScene’ Component Script, then the app crashes with an error:

Function Call Operator ( ) attempted on non-function. (runtime error &he0) in pkg:/components/RowListScene.brs(5)
005: utils = util()
Invoking util() here avoids that error, which makes me assume I would then need to pass it into the Scene using a GlobalNode.

sub SubscribeScreen()
    screen = CreateObject("roSGScreen")
    m.port = CreateObject("roMessagePort")
    screen.setMessagePort(m.port)
    scene = screen.CreateScene("RowListScene")
    
    utils = util()
    
    m.global = screen.getGlobalNode()
    m.global.id = "GlobalNode"
    m.global.addFields( {utils: utils} )
    
    screen.show()

    while(true)
        msg = wait(0, m.port)
        msgType = type(msg)
        if msgType = "roSGScreenEvent"
            if msg.isScreenClosed() then return
        end if
    end while
end sub
  1. The SceneGraph Component script - RowListScene.brs
function init()
    m.top.backgroundURI = "pkg:/images/background.png"
    
    utils = m.global.utils
    ' utils = util() -- this causes an error when invoked from this file
    
    print utils
    
    ...
end function

In turn, when I print out ‘utils’ up there, that is where I find:

<Component: roAssociativeArray> =
{
    getsomething: invalid
    printtest: invalid
}

Directly attempting to call utils.getsomething() or utils.printtest() in this file also results in error:

Member function not found in BrightScript Component or interface. (runtime error &hf4) in pkg:/components/RowListScene.brs(7)

007: utils.getsomething()
Does anything stick out as what might be causing the issue? It sounds like you’re suggesting I shouldn’t need to be using the ‘screen.getGlobalNode()’ function, is that correct?

Thanks for any help you are able to provide.

-Mikey

You know you can just include the file(s) containing your functions in tags, right?

MyScene.xml :


<?xml version="1.0" encoding="UTF-8"?>

<component name="MyScene" extends="Group">

    <script type="text/brightscript" uri="pkg:/components/MyScene.brs" />
    <script type="text/brightscript" uri="pkg:/components/HttpUtils.brs" />
    <script type="text/brightscript" uri="pkg:/source/ArrayUtils.brs" />
    <script type="text/brightscript" uri="pkg:/source/GeneralUtils.brs" />

I didn’t! So it sounds like it’s best practice that for any SceneGraph Component I create that needs access to my functions from util.brs, I would basically “require” them as a dependency as follows. Makes sense!

<script type="text/brightscript" uri="pkg:/components/util.brs" />

That is exactly what I was missing! Thank you belltown!

“joetesta” wrote:
rather than trying to include them as methods on the global noid, I believe you can (should?) just define global functions in source and reference them from anywhere.
For example, if you just include a file in your source folder called utils.brs and in there you can have a function;

Function myUtils()
    this = {
        someFunction : _someFunction
        getSomething : _getSomething
    }
    return this
End Function

Within the same file, define the functions ‘_someFunction’ and ‘_getSomething’
Now you can instantiate myUtils from anywhere ( util = myUtils() ) and pass in variables to the defined methods ( result = util.getSomething(somevars) ).
joetesta,

Your example hit upon nearly the same pattern i use for my brs modules. I don’t think it’s been formalized or blogged about anywhere but it would be nice if more Roku developers adopted this style. I’ll leave in-depth discussion for a different thread.

I’m attempting to use screen.getGlobalNode() to set a value I will need for my SceneGraph Screen. I’m finding that I can’t seem to update the value after it’s initially set.

  1. In SubscribeScreen.brs

This is my main BrightScript Component which creates the scene. In this class, I’m also setting a variable for ‘SubscriptionType’ using an external class that can’t seem to be called from RowListScene.brs

scene = screen.CreateScene("RowListScene")
...
if (m.global = invalid)
    m.global = screen.getGlobalNode()
    m.global.id = "GlobalNode"
end if

m.global.addFields( {userType: subscriptionType} )
  1. RowListScene.brs
function init()
    if (m.global.userType = "Monthly")
        ' code for monthly users
    end if
    
    ...

Setting the values works when the screen is first called, however, the screen can be opened more than once. For example, logging out and logging back in again.

Current attempts to log back in just freeze the app when hitting the line:

m.global.addFields

I can’t seem to call any roSGNode methods on m.global from SubscribeScreen.brs, however I can inside RowListScene.brs.

One method I thought I could use was calling m.global.Clear() to clear out the values whenever the screen is closed. This does clear the values out, but still doesn’t help in updating my ‘m.global.userType’ value on subsequent opens.

Any suggestions on how I could update a global node value would be very helpful.

Thank you,

-Mikey

Perhaps some more code would help understand what you’re trying to do. I don’t see any code where you’re setting userType after you’ve initialized it, and I don’t see any code in RowListScene where you’re observing the field for changes.

Here’s what the original code looked like for setting the userType after it was initialized:

if (m.global = invalid)
    m.global = screen.getGlobalNode()
    m.global.id = "GlobalNode"
    m.global.addFields( {userType: "Trial"} )
else
    m.global.userType = "Monthly"
end if

I hadn’t thought about adding an observer to RowListScene.brs. But if I do, would the code below pick up changes to when the ‘else’ statement above has executed:

m.global.observeField("userType", "userTypeChanged")

function userTypeChanged()
    print "USER TYPE HAS CHANGED"
    m.userType = m.global.userType
end function

Thanks for the help!
-Mikey

That’s the idea. Try it and see what happens.

I just had the chance to try and my observer is not working. Or at least what I’m finding is that it’s not being dispatched because ‘m.global.usertype’ isn’t actually changing.

The code looks to be getting executed, however, when I output the ‘m.global’ SG Node afterward, I receive the following:

The ‘usertype’ property is there, but still contains the value it was initialized at, which tells me there is something wrong with how I’m attempting to set the value in my else block.

I can comment up my code to hopefully give you a better idea of what I’m trying to achieve:

SubscribeScreen.brs

if (m.global = invalid)
    ' This block executes when the user opens the screen for the first time
    m.global = screen.getGlobalNode()
    m.global.id = "GlobalNode"
    m.global.addFields( {userType: "Preview"} ) ' This line adds "userType": "Preview" to the m.global roSGNode
else
    ' on subsequent opens, 'm.global' has been initialized, so we just want to update its 'userType' value which could have changed 
    m.global.userType = "Monthly"
end if

RowListScene.brs

function init()
    m.top.backgroundURI = "pkg:/images/background.png"
    m.top.backExitsScene = true

    m.global.observeField("userType", "userTypeChanged") ' setting up my observer in the init() function
    ...
end function

' defining my observer handler
function userTypeChanged()
    ' but I never see this called, most likely because I have never seen the value update
    print "USER TYPE HAS CHANGED"
    m.userType = m.global.userType
end function

Have you confirmed that the code m.global.userType = “Monthly” is actually executing?

Only in that a print statement will log out when placed in that else block. Also, the app doesn’t get hung up. It’s an assumption, but I’m unsure how I would 100% confirm.

If I place print statements before & after the line:
m.global.userType = “Monthly”
Both print statements log out in my debugger. So I am inclined to think that the code is being executed, but again it is just an assumption.

You could put a print statement in SubscribeScreen.brs to see if it is getting initialized again.

I found my solution and I think I need to abandon any hope of using the Global Node. I went with adding a new field to my screens interface:

I was able to modify this value by setting it right before the while loop (and after the screen.Show() method) of my SubscribeScreen.brs file:
scene.userType = m.context.subscriptionType

Which I was then able to add an observer if any changes were made to “m.top.userType” within RowListScene.brs:
m.top.observeField(“usertype”, “userTypeChanged”)

“Motorcykey” wrote:
I didn’t! So it sounds like it’s best practice that for any SceneGraph Component I create that needs access to my functions from util.brs, I would basically “require” them as a dependency as follows. Makes sense!


It is not a “best practice” - it is “the only practice”, since there is no other way to do it. Each XML component you define sees only the scripts explicitly pointed at, each lives in its own little world.

And for amusement’s sake, i can say that this is also a “worst practice”, just like it is a “best practice” - when you have freedom of choice of only 1 item, it’s both the best and the worst in the series :mrgreen:

“Veeta” wrote:

“joetesta” wrote:
… Within the same file, define the functions ‘_someFunction’ and ‘_getSomething’
Now you can instantiate myUtils from anywhere ( util = myUtils() ) and pass in variables to the defined methods ( result = util.getSomething(somevars) ).
Your example hit upon nearly the same pattern i use for my brs modules. I don’t think it’s been formalized or blogged about anywhere but it would be nice if more Roku developers adopted this style. I’ll leave in-depth discussion for a different thread.
There is very little to no utility in doing so if there is no real data in the object.
Why do it then? You cannot pass the resulting AA outside scope (because: RSG) and inside the scope the “underscore” functions are already visible, so…

“Motorcykey” wrote:
Setting the values works when the screen is first called, however, the screen can be opened more than once. For example, logging out and logging back in again.

Current attempts to log back in just freeze the app when hitting the line:

m.global.addFields

I can’t seem to call any roSGNode methods on m.global from SubscribeScreen.brs, however I can inside RowListScene.brs.
Wait a minute - are you telling me that you create >1 roSgScreen - as in, multiple times creating and closing a roSgScreen? And that you expect .getGlobalNode() on different RSG screens to return the same value (and not say, alternatively hang or crash because nobody has tried doing such crazy thing :slightly_smiling_face: )?