Your Digital Media Has Never Looked So Good

 
sdpetersen
Topic Author
Posts: 6
Joined: Fri Oct 26, 2018 5:33 am

Passing a variable out of a function

Fri Oct 26, 2018 5:53 am

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!
 
User avatar
RokuNB
Posts: 446
Joined: Fri Mar 31, 2017 2:22 pm

Re: Passing a variable out of a function

Fri Oct 26, 2018 6:16 pm

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
 
sdpetersen
Topic Author
Posts: 6
Joined: Fri Oct 26, 2018 5:33 am

Re: Passing a variable out of a function

Mon Oct 29, 2018 5:08 am

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?
 
sdpetersen
Topic Author
Posts: 6
Joined: Fri Oct 26, 2018 5:33 am

Re: Passing a variable out of a function

Tue Oct 30, 2018 9:18 pm

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 :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.

Who is online

Users browsing this forum: No registered users and 3 guests