Roku Developer Program

Developers and content creators—a complete solution for growing an audience directly.
cancel
Showing results for 
Search instead for 
Did you mean: 
Motorcykey
Level 7

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
0 Kudos
19 Replies
joetesta
Level 10

Re: Accessing a global object inside a SceneGraph Component

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) ).
aspiring
0 Kudos
Motorcykey
Level 7

Re: Accessing a global object inside a SceneGraph Component

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



2) 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



3) 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
0 Kudos
belltown
Level 7

Re: Accessing a global object inside a SceneGraph Component

You know you can just include the file(s) containing your functions in <script> 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" />
https://github.com/belltown/
0 Kudos
Motorcykey
Level 7

Re: Accessing a global object inside a SceneGraph Component

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" />
0 Kudos
Motorcykey
Level 7

Re: Accessing a global object inside a SceneGraph Component

That is exactly what I was missing! Thank you belltown!
0 Kudos
Veeta
Level 7

Re: Accessing a global object inside a SceneGraph Component

"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.
0 Kudos
Motorcykey
Level 7

Re: Accessing a global object inside a SceneGraph Component

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} )



2) 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
0 Kudos
belltown
Level 7

Re: Accessing a global object inside a SceneGraph Component

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.
https://github.com/belltown/
0 Kudos
Motorcykey
Level 7

Re: Accessing a global object inside a SceneGraph Component

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
0 Kudos