on the corono busines app sample, feed and feed 2

TABLE VIEW LISTENER CODE.

I’m having a problem when touching an area of the tableview that isn’t populated with a row. If that area is touched, it returns an error: attempt to call method ‘getContentPosition’ (a nil value).

Any idea how to get around that?

 

[lua]-- Project: Business Sample App

– File name: feed.lua

– Author: Corona Labs

– Abstract: Read an RSS Feed.

– Target devices: simulator, device

– Sample code is MIT licensed, see http://www.coronalabs.com/links/code/license

– Copyright © 2013 Corona Labs Inc. All Rights Reserved.


–[[

Permission is hereby granted, free of charge, to any person obtaining a copy of

this software and associated documentation files (the “Software”), to deal in the

Software without restriction, including without limitation the rights to use, copy,

modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,

and to permit persons to whom the Software is furnished to do so, subject to the

following conditions:

The above copyright notice and this permission notice shall be included in all copies

or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,

INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR

PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE

FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR

OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER

DEALINGS IN THE SOFTWARE.

]]–


local composer = require( “composer” )

local scene = composer.newScene()

local socket = require( “socket” )

local widget = require( “widget” )

– if you have an Atom feed uncomment this and comment out the line after it.

– local rss = require(“atom”)

local atom = require( “youtube” )

local myApp = require( “myapp” )

local utility = require( “utility” )

widget.setTheme(myApp.theme)

– forward declarations

local feedName

local feedURL

local displayMode 

local pageTitle 

local icons

local params

local springStart

local needToReload

local spinner

local myList = nil

local stories = {}

local listGroup

local imageList = {}

– make a quick network connection to your server… 

– this requires the site’s DNS Name, not a URL, in other words leave off

– the http:// and no trailing things like /index.php

local function testNetworkConnection()

        local netConn = socket.connect(‘www.google.com’, 80)

        if netConn == nil then

             return false

        end

        netConn:close()

        return true

end

– this function gets called when we tap on a row.

local onRowTouch = function( event )

    if event.phase == “release” then

        

        local id = event.row.index

        local story = event.target.params.story

        local params = {

            story = story

        }

        local options = {

            effect = “slideLeft”,

            time = 250,

            isModal = true,

            params = params

        }

        composer.showOverlay(displayMode, options)

    end

    return true

end

– 

– This function is used to draw each row of the tableView

    

local function onRowRender(event)

    print(“row render”)

    –

    – set up the variables we need that are passed via the event table

    –

    local row = event.row

    local story = event.row.params.story

    local id = row.index

    –

    – boundry check to make sure we are tnot trying to access a story that

    – doesnt exist.

    –

    if id > #stories then return true end

    row.bg = display.newRect(0, 0, display.contentWidth, 60)

    row.bg.anchorX = 0

    row.bg.anchorY = 0

    row.bg:setFillColor( 1, 1, 1 )

    row:insert(row.bg)

    

    –

    – attempt to get an enclosure to render in the row

    –

    –

    – display.loadRemoteImage creates a display object, but you can only access it in the event call back

    – so we need a function to handle rendering the row.

    –

    local function thumbListener( event )

        if ( event.isError ) then

            print ( “Network error - download failed” )

        else

            print (“got image”)

            

            event.target.alpha = 0

            –

            – This is scaling code.  I have no idea how big the downloaded image was.  

            – Divide the desired height of the image by the real height and that gives us a number we

            – can feed to the scale() method to keep the image’s aspect ratio and controlling the height.

            – If you need to furhter control the width, you could do a second pass kind of like this:

            –

            – if w * s > maxWidth then s = maxWidth / w end

            –

            – and feed the new s value to the scale() method.

            –

            local w = event.target.width

            local h = event.target.height

            local s = itemIcon.height / h

            event.target:scale(s,s)

            event.target.anchorX = 0

            event.target.anchorY = 0

            event.target.x = 2

            event.target.y = 4

            –

            – put the image into the row

            –

            row:insert(event.target)

            transition.to( event.target, {time=100, alpha = 1.0 } )

        end

        print ( "RESPONSE: " … event.response )

    end

    – check to see if I’m using embedded icons or showing something fixed.

    if icons == “embedded” then

        –

        – lets check to see if we have any enclosures.  The entry must not be nil and there has to be at least one entry

        – (it is possible for enclosures to have an empty table that wouldn’t be nil)

        if story.enclosures and #story.enclosures > 0 then

            – check to see if it’s an image.

            – an entry can have multiple enclosures.  They may not all be something we can display (audio, etc.) so loop 

            – over the enclosures and look for anything that is a type we can suppport.   This is case sensitive and I don’t

            – know if every feed sends them in lower case.

            –

            local found = false

            local j = 0

            while j < #story.enclosures and not found do

                j = j + 1

                local e = story.enclosures[j]

                if e.type == “image/jpeg” or e.type == “image/jpg” or e.type == “image/png” then

                    –

                    – Ah Ha! we have a potentially displayable image

                    – create a local filename.  I suppose I could parse it out of the URL, but it really doesn’t matter

                    – so make up one, but I do need to parse the extension off of the type.

                    local filename = string.format(“image_%3d.%s”,id, string.sub(e.type, string.find(e.type,"/") + 1))

                    –

                    – Now make Corona SDK do all the heavy lifting for me.  This little gem will fetch the URL, 

                    – store it in the Caches directory (to make Apple happy) and when complete it will call a function that

                    – will shove it into our row for us.  If the image is bad, or doesn’t exist, then it won’t display anything

                    – though it should drop a message into the console log.

                    –

                    display.loadRemoteImage( e.url, “GET”, thumbListener, filename, system.CachesDirectory, 0, 0 )

                end

            end

        end

    else

        –

        – If you want an icon on the left side of the table view, define it here

        – it uses the table above to get all the information we need

        –

        row.icon = display.newImageRect(myApp.icons, 12 , 40, 40 )

        row.icon.x = 20

        row.icon.y = row.height / 2

        row:insert(row.icon)

    end

    –

    – Figure out how long I can make my titles

    –

    local titleLength = math.floor(display.contentWidth / 11) - 3

    print("titleLength ", titleLength)

    print(“CL”, display.contentWidth)

    –

    – Now create the first line of text in the table view with the headline

    – of the story item.

    –

    local myTitle = story.title

    if string.len(myTitle) > titleLength then

        myTitle = string.sub(story.title, 1, titleLength) … “…”

    end

    row.title = display.newText( myTitle, 12, 0, myApp.fontBold, 18 )

    row.title.anchorX = 0

    row.title.anchorY = 0.5

    row.title:setFillColor( 0 )

    row.title.y = 22

    row.title.x = 42

    

    –

    – show the publish time in grey below the headline

    –

    –

    – 2014-10-01T20:23:40+00:00

    local year, month, day, hour, min = string.match(story.pubDate,"(%d+)-(%d+)-(%d+)T(%d+):(%d+):")

    row.subtitle = display.newText( month…"/"…day…"/"…year…" “…hour…”:"…min, 12, 0, myApp.font, 14)

    row.subtitle.anchorX = 0

    row.subtitle:setFillColor( 0.375, 0.375, 0.375 )

    row.subtitle.y = row.height - 18

    row.subtitle.x = 42

    --]]

    –

    – Add a graphical right arrow to the right side to indicate the reader

    – should touch the row for more information

    –

    row.rightArrow = display.newImageRect(myApp.icons, 15 , 40, 40)

    row.rightArrow.x = display.contentWidth - 20

    row.rightArrow.y = row.height / 2

    – must insert everything into event.view:

    row:insert(row.title )

    row:insert(row.subtitle)

    row:insert(row.rightArrow)

    return true

end

– load up our tableiew and manage it

local function showTableView()

    print(“Calling showTableView()”)

    – 

    – now the fun part!  loop over the number of stories returned in the feed

    – and do an insertRow for each table item.  Note that we specify the function 

    – to do when the row is tapped on and a function to do to render each row.

    

    for i = 1, #stories do

        print(“insert row:  " … i … " [” … stories[i].title … “]”)

        

        myList:insertRow{

            rowHeight = 60,

            isCategory = false,

            rowColor = { 1, 1, 1 },

            lineColor = { 0.90, 0.90, 0.90 },

            params = {

                story = stories[i]

            }

        }

    end

    – cancel the busy indicator

    --native.setActivityIndicator( false )

end

– since we are basically re-entering the same scene over and over, we have to

– dump the tableView and rebuild it each time so that it functions correctly

local function purgeList(list)

    list:deleteAllRows()

end

– function to fetch the feed, parse it and setup the display

– This has to be read kind of backwards based on the flow of things.  

– 

– First, we need to check for network avaialbility.  That will trigger a

– call back function, that will start the download.  Once the download is 

– donn, another call back will trigger the parsing of the RSS feed, then when

– that is done, the tableView is loaded up with entries and displayed.

– if the network is  unavialable or if the downloas fails we will try to use

– the cached version of the file.  

– I should point out that this system supports both RSS2.0 and Atom feeds.

– Both are RSS feeds, but they are slightly different and enough that the same

– parser won’t deal with both feeds.  So you can substitude 

function displayFeed(feedName, feedURL)

    native.setActivityIndicator(true)

    print(“entering displayFeed”, feedName, feedURL)

    – 

    – this will process the file and return a table with the feed information

    – a member of that table, named items, is a table with each story returned

    – from the feed.  

    – Then we initialize the tableView.

    –

    local function processRSSFeed(file, path)

        native.setActivityIndicator(false)

        print(“Parsing the feed”)

        local story = {}

        local feed = atom.feed(file, path)

        

        stories = feed.items

        --utility.print_r(stories)

        print("Num stories: " … #stories)

        print("Got “, #stories, " stories, now show the tableView”)

        purgeList(myList)

        showTableView()

    end

    

    local function onAlertComplete( event )

        return true

    end

    

    –

    – Ah ha, our download is finished.  Maybe.  If it is, process the feed.

    

    local networkListener = function( event )

       

        if ( event.isError ) then

            local alert = native.showAlert( “CoronaSDK”, “Feed temporarily unavaialble.”, 

                                        { “OK” }, onAlertComplete )

        else

            print(“calling processRSSFeed because the feed is avaialble”)

            processRSSFeed(feedName, system.CachesDirectory)

        end

        return true

    end

    

    –

    – cool we can reach the network, now request the file.  If the network can’t

    – be reached, then check for the cached version of the file.  

    –

    

    local isReachable = testNetworkConnection()

    if isReachable then

        – download the latest file

        – show some indicator that we are busy

        --native.setActivityIndicator( true )

        – uncomment the line above if you want an activity indicator but it causes

        – the display to flash if it downloads the file too quickly

        – likewise if you do, you have to uncomment the other setActivityIndicator

        – above.

        network.download(feedURL, “GET”, networkListener, feedName, system.CachesDirectory)

    else

        print(“not reachable”)

        – look for an existing copy

       

        local path = system.pathForFile(feedName, system.CachesDirectory)

        

        local fh, errStr = io.open( path, “r” )

        if fh then

            io.close(fh)

            print(“calling processRSSfeed because the network isn’t reachable”)

            processRSSFeed(feedName, system.CachesDirectory)

        else

            local alert = native.showAlert( “CoronaSDK”, “Feed temporarily unavaialble.”, 

                                        { “OK” }, onAlertComplete )

        end

    end

    return true

   

end

local function reloadTable()

    –

    – check the URL and see if it has a query string, if not add one, if it does just add 

    – our cache buster string

    – 

    local cacheBustedURL = feedURL

    if string.find(cacheBustedURL,"%?") then

        – we have a query string

        cacheBustedURL = cacheBustedURL … “&cacheBust=” … tonumber(os.time())

    else

        cacheBustedURL = cacheBustedURL … “?cacheBust=” … tonumber(os.time())

    end

    displayFeed(feedName, cacheBustedURL )

end

local function tableViewListener(event)

    print(“tableViewListener”, event.phase, event.direction, event.limitReached, myList:getContentPosition( ))

    if event.phase == “began” then

        springStart = event.target.parent.parent:getContentPosition( )

        print(“springStart”, springStart)

        needToReload = false

        spinner.isVisible = true

        spinner:start()

    elseif event.phase == “moved” then

        if event.target.parent.parent:getContentPosition( ) < springStart + 60 then

            needToReload = true

            --print(“needToReload”, needToReload, myList:getContentPosition( ), springStart + 60)

        end

    elseif event.phase == nil and event.direction == “down” and event.limitReached == true and needToReload then

        --print(“reloading Table!”)

        needToReload = false

        spinner:stop()

        spinner.isVisible = false

        reloadTable()

    end

    return true

end

– Start the composer event handlers

function scene:create( event )

    local sceneGroup = self.view

    params = event.params

        

    –

    – setup a page background, really not that important though composer

    – crashes out if there isn’t a display object in the view.

    –

    print(“create scene”)

    local background = display.newRect(0,0,display.contentWidth, display.contentHeight)

    background:setFillColor( 0.95, 0.95, 0.95 )

    background.x = display.contentWidth

    background.y = display.contentHeight 

    sceneGroup:insert(background)

    local navBar = widget.newNavigationBar({

        title = “Centra Care Minstry Video”,

        backgroundColor = { 0, 120, 255 },

        titleColor = {0, 0, 0},

        font = “HelveticaNeue”

    })

    sceneGroup:insert(navBar)

    –

    – Create an invisible button to reload our table over top of the status bar

    –

    local reloadBar = display.newRect(display.contentCenterX, display.topStatusBarContentHeight * 0.5, display.contentWidth, display.topStatusBarContentHeight)

    reloadBar.isVisible = false

    reloadBar.isHitTestable = true

    reloadBar:addEventListener( “tap”, reloadTable )

    local box = display.newRect(display.contentCenterX, display.contentCenterY, display.contentWidth, display.contentHeight - navBar.height - 50)

    box:setFillColor( 0.8, 0.8, 0.8 )

    box.anchorY = 0

    box.y = navBar.height

    sceneGroup:insert(box)

    spinner = widget.newSpinner({ 

        width = 32, 

        height = 32, 

    })

    sceneGroup:insert(spinner)

    spinner.x = display.contentCenterX

    spinner.y = navBar.height + 20

    spinner.isVisible = false

    –

    – Create the table view.  After the height of the top bar, the bottom tabBar

    – and room for the ad, we have room for a 320x320 area to hold the table view.

    – Since we are not using the whole screen we will need a mask file.

    –

    – build a new tableView

    local tWidth = display.contentWidth

    local tHeight = display.contentHeight - navBar.height - myApp.tabBar.height

    myList = widget.newTableView{ 

        top = navBar.height, 

        width = tWidth, 

        height = tHeight, 

        maskFile = maskFile,

        listener = tableViewListener,

        hideBackground = true, 

        onRowRender = onRowRender,

        onRowTouch = onRowTouch 

    }

    –

    – insert the list into the group

    sceneGroup:insert(myList)

end

function scene:show( event )

    local sceneGroup = self.view

    

    params = event.params

    print(“enter scene”)

    – fetch the parameters from the composer table for this view

    

    feedName = params.feedName

    feedURL = params.feedURL

    displayMode = params.displayMode

    pageTitle = params.pageTitle

    icons = params.icons

    –

    – go fetch the feed

    –

    if event.phase == “did” then

        print(“show”, feedName,feedURL)

        displayFeed(feedName, feedURL)

    end

end

function scene:hide( event )

    local sceneGroup = self.view

    

    – get out of here.

    – dump the table entries

    –

    if event.phase == “will” then

        print(“exit scene”)

        purgeList(myList)

    end

end

function scene:destroy( event )

    local sceneGroup = self.view

    

    print(“destroy scene”)

end


– END OF YOUR IMPLEMENTATION


– “createScene” event is dispatched if scene’s view does not exist

scene:addEventListener( “create”, scene )

– “enterScene” event is dispatched whenever scene transition has finished

scene:addEventListener( “show”, scene )

– “exitScene” event is dispatched before next scene’s transition begins

scene:addEventListener( “hide”, scene )

– “destroyScene” event is dispatched before view is unloaded, which can be

– automatically unloaded in low memory situations, or explicitly via a call to

– composer.purgeScene() or composer.removeScene().

scene:addEventListener( “destroy”, scene )


return scene[/lua]

Can you be more descriptive of what you mean by touching areas of the tableView that are not populated? Perhaps a screen shot showing where you are touching?

Rob

the white one is fine…

but the grey one… when i touch it it will show error code.

error: attempt to call method ‘getContentPosition’ (a nil value)…

Replace the function “tableViewListener” with this one:
 

local function tableViewListener(event) print("tableViewListener", event.phase, event.direction, event.limitReached, myList:getContentPosition( )) if event.phase == "began" then local currentPosition = nil if event.target.parent.parent.getContentPosition then currentPosition = event.target.parent.parent:getContentPosition( ) end springStart = currentPosition print("springStart", springStart) needToReload = false spinner.isVisible = true spinner:start() elseif event.phase == "moved" then local currentPosition = nil if event.target.parent.parent.getContentPosition then currentPosition = event.target.parent.parent:getContentPosition( ) end if currentPosition and springStart and currentPosition \> springStart + 60 then needToReload = true --print("needToReload", needToReload, myList:getContentPosition( ), springStart + 60) end elseif event.phase == nil and event.direction == "down" and event.limitReached == true and needToReload then --print("reloading Table!") needToReload = false spinner:stop() spinner.isVisible = false reloadTable() end return true end

You will need to replace it in both feed.lua and feed2.lua.

Rob

wow You re so awesome rob…

thank you so much… very helpful

Thanks for finding the bug!

Rob

Can you be more descriptive of what you mean by touching areas of the tableView that are not populated? Perhaps a screen shot showing where you are touching?

Rob

the white one is fine…

but the grey one… when i touch it it will show error code.

error: attempt to call method ‘getContentPosition’ (a nil value)…

Replace the function “tableViewListener” with this one:
 

local function tableViewListener(event) print("tableViewListener", event.phase, event.direction, event.limitReached, myList:getContentPosition( )) if event.phase == "began" then local currentPosition = nil if event.target.parent.parent.getContentPosition then currentPosition = event.target.parent.parent:getContentPosition( ) end springStart = currentPosition print("springStart", springStart) needToReload = false spinner.isVisible = true spinner:start() elseif event.phase == "moved" then local currentPosition = nil if event.target.parent.parent.getContentPosition then currentPosition = event.target.parent.parent:getContentPosition( ) end if currentPosition and springStart and currentPosition \> springStart + 60 then needToReload = true --print("needToReload", needToReload, myList:getContentPosition( ), springStart + 60) end elseif event.phase == nil and event.direction == "down" and event.limitReached == true and needToReload then --print("reloading Table!") needToReload = false spinner:stop() spinner.isVisible = false reloadTable() end return true end

You will need to replace it in both feed.lua and feed2.lua.

Rob

wow You re so awesome rob…

thank you so much… very helpful

Thanks for finding the bug!

Rob