Forum Discussion

sdpetersen's avatar
sdpetersen
Visitor
7 years ago

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!

3 Replies

  • "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
  • 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?
  • 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.