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: 
sdpetersen
Level 7

Passing a variable out of a function

I'm making an API call to gather associative arrays of content to display in my channel. I'm doing all this through a Task, but I'm running into trouble getting the variables back to the Main script for use.

In a nutshell, I have this function called from the Init of the navigation.brs file like so:
Sub Init()
    m.navItems = m.top.findNode("navigation")
    m.top.functionName = "loadNavContent"
End Sub

Sub loadNavContent()

    oneRow = APICall("main-navigation")
    
    movieArray = {}
    for each jsonItem in oneRow
        if jsonItem.DoesExist("api")
            movieArray[jsonItem.id] = loadMovieContent(jsonItem.api)
            
            m.movies = m.top.findNode(jsonItem.id) 
            m.movies = movieArray[jsonItem.id]
            m.movies.id = jsonItem.id
        end if
    end for

End Sub


APICall() is a function that makes an API call that returns a dynamic list of content that should be loaded (it changes based on the user). loadMovieContent() is another function that makes another API call to get the content (lists of movies) I'm looking to store, based on the APICall() result. Both of these are working fine.

My problem is, I cannot access m.movies ( m.top.findNode(jsonItem.id) ) outside of this function. What I don't understand is if I make the assignment like this I can access it outside of the function without problems:
m.top.movieContent = movieArray[jsonItem.id]


I've read bits in the Roku documentation that suggest that m.top.movieContent and m.top.findNode("movieContent") are synonymous, however I'm finding that's not true, at least not in terms of scope of the variable.

First, can you help me find a way of assigning dynamic variables that I can access outside of the function?

Next, I'd love to read up on the scope of variables, if someone has a link to read (I'm not impressed with what I'm finding on the Roku documentation site)

Finally, I'm a PHP dev that's making this up as I go, so if you see a better way of doing things, please, point me in the right direction.

Much appreciated!
0 Kudos
3 Replies
Roku Employee
Roku Employee

Re: Passing a variable out of a function

"sdpetersen" wrote:
I've read bits in the Roku documentation that suggest that m.top.movieContent and m.top.findNode("movieContent") are synonymous, however I'm finding that's not true, at least not in terms of scope of the variable.

No, they are not synonymous.
m.top.movieContent accesses attribute/field of the current custom/xml component.
m.top.findNode("movieContent") searches recursively for a node by given ID attribute.

First, can you help me find a way of assigning dynamic variables that I can access outside of the function?

Use task's m.top for that. Use `m` to store tasks variables and `m.top` for dealing with external threads. I'd

  • add 2 fields, m.top.addFields({inbox: {}, outbox: {}})

  • m.top.observerField("inbox", somePort)

  • run a message loop waiting for commands getting sent (by assigning) to `inbox`

  • do the job and assign the result to `outbox`

  • externally, another thread is observing `outbox` and on result arrival, do what it wanted to
0 Kudos
sdpetersen
Level 7

Re: Passing a variable out of a function

Thanks for your reply.

I think what you're recommending make sense... can you point me to some sample code that shows this process working?
0 Kudos
sdpetersen
Level 7

Re: Passing a variable out of a function

I figured it out, thanks for your help!

For anyone who finds this later, here's some sample code (because everything's better with a sample Smiley LOL):

Disclaimer: This is has been simplified for posting here and is not guaranteed to run as-is, but should give you the general idea of how to do things.

loadMovies.xml
<?xml version="1.0" encoding="UTF-8"?>
<component name="Movies" extends="Task">
<interface>
<field id="inbox" type="node" />
</interface>
<script type="text/brightscript" uri = "pkg:/components/Movies/loadMovies.brs"/>
</children>
</component>


loadMovies.brs
Sub Init()
    m.navItems = m.top.findNode("Movies")
    m.top.functionName = "loadNavContent"
    m.top.observeField("inbox","loadContent")
End Sub

Sub loadContent()
    m.movies = m.top.inbox
    m.content = m.top.findNode(m.movies.id)
    m.content = m.movies
End Sub

Sub loadNavContent()
    oneRow = APICall("main-navigation") 'Make an API call to fetch the lists of movies you will be loading
    
    movieArray = {}
    for each jsonItem in oneRow
        if jsonItem.DoesExist("api")
            movieArray[jsonItem.id] = loadMovieContent(jsonItem.api) 'Make an API call for each item returned in the "main-navigation" API Call
            
            m.movies = CreateObject("roSGNode", "ContentNode") 
            m.movies = movieArray[jsonItem.id]
            m.movies.id = jsonItem.id
            
            m.top.inbox = m.movies 'Assign the movie Node to the inbox for catching and processing from the main script
        end if
    end for
End Sub

Function APICall(action as string) 'This function retrieves and parses the feed and stores each content item in a ContentNode
    account = [INSERT ACCOUNT TOKEN HERE]
    
    url = CreateObject("roUrlTransfer") 'component used to transfer data to/from remote servers
    url.SetCertificatesFile("common:/certs/ca-bundle.crt")
    url.AddHeader("X-Roku-Reserved-Dev-Id", "")
    url.InitClientCertificates()
    requestUrl = Substitute("https://[YOUR-WEBSITE-HERE]?accountToken={1}&action={0}", action, account)
    url.SetUrl(requestUrl)
    
    rsp = url.GetToString()
    responseJSON = parseJSON(rsp)
    
    responseArray = responseJSON.value
    
    return responseArray
    
End Function

Function loadMovieContent(movieAction as string)
    myMovies = GetMovieFeed(movieAction)
    rows = {}
    myMoviesList = []
    
    for each row in myMovies.keys() '.key() forces the associative array to loop in order
       rowGroup = {
           Title:row
           ContentList:myMovies[row]
       }
       myMoviesList.push(rowGroup)
    end for
    
    return ParseXMLContent(myMoviesList)
End Function

Function GetMovieFeed(callType as string) 'This function retrieves and parses the feed and stores each content item in a ContentNode
    account = [INSERT ACCOUNT TOKEN HERE]
    
    url = CreateObject("roUrlTransfer") 'component used to transfer data to/from remote servers
    url.SetCertificatesFile("common:/certs/ca-bundle.crt")
    url.AddHeader("X-Roku-Reserved-Dev-Id", "")
    url.InitClientCertificates()
    requestUrl = Substitute("https://[YOUR-WEBSITE-HERE]?accountToken={0}&action={1}&group=alpha", account, callType)
    url.SetUrl(requestUrl)
    
    rsp = url.GetToString()
    responseJSON = parseJSON(rsp)
    
    responseArray = responseJSON.value
    
    result = {}
    
    for each row in responseArray
        itemGroup = []
        for each jsonItem in responseArray[row]
            item = {}
            item.ContentType = 1 'Roku Values: 1 = Movie, 2 = Series, 3 = Season, 4 = Episode, 5 = Audio, as per https://sdkdocs.roku.com/display/sdkdoc/Content+Meta-Data
            item.title = jsonItem.title
            item.description = strReplace(jsonItem.description,"<br><br>", chr(10))
            item.Rating = jsonItem.item_rating
            item.NumEpisodes = jsonItem.duration
            item.StarRating = jsonItem.item_rate
            item.Categories = jsonItem.genre
            item.Directors = jsonItem.director
            item.Actors = jsonItem.actors
            item.url = jsonItem.streams.default
            'item.stream = {url : jsonItem.streams.default}
            item.streamFormat = "mp4"
            item.HDPosterURL = jsonItem.poster
            item.HDBackgroundImageUrl = jsonItem.poster1
            
            itemGroup.push(item)
        end for
        result[row] = itemGroup
    end for
    
    return result
    
End Function

Function ParseXMLContent(list As Object) 'Formats content into content nodes so they can be passed into the RowList
    RowItems = createObject("RoSGNode","ContentNode")
    'Content node format for RowList: ContentNode(RowList content) --<Children>-> ContentNodes for each row --<Children>-> ContentNodes for each item in the row)
    for each rowAA in list
        row = createObject("RoSGNode","ContentNode")
        row.Title = rowAA.Title

        for each itemAA in rowAA.ContentList
            item = createObject("RoSGNode","ContentNode")
            item.SetFields(itemAA)
            row.appendChild(item)
        end for
        RowItems.appendChild(row)
    end for
    return RowItems
End Function

'******************************************************
'Replace substrings in a string. Return new string
'******************************************************
Function strReplace(basestr As String, oldsub As String, newsub As String) As String
    newstr = ""

    i = 1
    while i <= Len(basestr)
        x = Instr(i, basestr, oldsub)
        if x = 0 then
            newstr = newstr + Mid(basestr, i)
            exit while
        endif

        if x > i then
            newstr = newstr + Mid(basestr, i, x-i)
            i = x
        endif

        newstr = newstr + newsub
        i = i + Len(oldsub)
    end while

    return newstr
End Function

*Those last 2 functions are borrowed from a Roku example

HomeScene.brs (just the pertinent functions):
Sub init()
    
    m.RowList = m.top.findNode("RowList")
    
    m.navigation = CreateObject("roSGNode", "Movies") 'Create API Call and Parsing task node
    m.navigation.control = "RUN" 'Run the task node
    m.navigation.observeField("inbox","movieCollection") 'Listen for changes to the inbox

    m.RowList.setFocus(true)

End Sub

Sub movieCollection()
    
    m.newMovies = m.navigation.inbox
    m.movies = CreateObject("roSGNode", "ContentNode") 'Create a new node for the movies
    m.movies = m.newMovies
    m.movies.id = m.newMovies.id
    m.top.appendChild(m.movies) 'Add the new movies to the parent node for later use
    m.findMovies = m.top.findNode(m.newMovies.id)
    
End Sub

Function onKeyEvent(key as String, press as Boolean) as Boolean

    'Do some processing to tell it what node to display
    'Example:
    'm.newMovies = m.top.findNode(m.mainNavItems.getChild(m.mainNavItems.buttonFocused).getField("id")) 'Grab the ID of the select navMenu item           
    'm.RowList.content = m.newMovies
    'm.RowList.setFocus(true)

End Function


I don't claim this is the best way to do it, but it does work for me and my purposes and I welcome any constructive criticism that will make this better.
0 Kudos