Tableview crash when asynch download image in row (bad argument #-1 to 'newImage' (Proxy expected, got nil))

Hello,

I migrated from v2017.3073 to v2018.3204, and a lot of runtime errors appears in my app.

I isolated the problem and tried to reproduce the issue in the sample “Widget” Corona (I reduce the code to a minimum).

It looks like the runtime error is related to the asynchronous image loading I do in the onRowRender function of the TableView.

When I manipulate quickly the tableView up and down, the runtime error occure.

ERROR: Runtime error

main.lua:40: bad argument #-1 to ‘newImage’ (Proxy expected, got nil)

It seems that sometimes the var “row” (event.row) used to attach image is not defined, i don’t know why. I didn’t find any solution.

am i doing something wrong? is it a bug i have to report?

In advance thank you to the person who will take the time to read my post and will bring me a beginning of solution.

PS: sorry for my bad english.

Here is the code of my main.lua

Have a nice day!

local widget = require( "widget" ) -- Forward reference for the tableView local tableView -- Listen for tableView events local function tableViewListener( event ) local phase = event.phase --print( "Event.phase is:", event.phase ) end -- Handle row rendering local function onRowRender( event ) local phase = event.phase local row = event.row local groupContentHeight = row.contentHeight local rowTitle = display.newText( row, "Row " .. row.index, 0, 0, nil, 14 ) rowTitle.x = 10 rowTitle.anchorX = 0 rowTitle.y = groupContentHeight \* 0.5 if ( row.isCategory ) then rowTitle:setFillColor( 1 ) rowTitle.text = rowTitle.text.." (category)" else rowTitle:setFillColor( 0 ) --[[ASYNC DOWLOAD IMAGE]] --[[CODE FROM THE SAMPLE AsynchImageDownload]] local myImage local function networkListener( event ) if ( event.isError ) then print ( "Network error - download failed" ) else myImage = display.newImage(row, event.response.filename, event.response.baseDirectory, 20, 20) myImage.alpha = 0 myImage.x = display.contentWidth - 50 myImage.y = 75 myImage.width = 120 myImage.height = 120 transition.to( myImage, { alpha = 1.0 } ) print ( "RESPONSE: ", event.response.filename ) end end network.download( "https://developer.coronalabs.com/demo/hello.png", "GET", networkListener, "helloCopy.png", system.TemporaryDirectory ) end --[[/ ADD BY ME]] end -- Handle row updates local function onRowUpdate( event ) local phase = event.phase local row = event.row --print( row.index, ": is now onscreen" ) end -- Handle touches on the row local function onRowTouch( event ) local phase = event.phase local row = event.target if ( "release" == phase ) then end end -- Create a tableView tableView = widget.newTableView { top = 0, left = 0, width = display.contentWidth, height = display.contentHeight, hideBackground = true, listener = tableViewListener, onRowRender = onRowRender, onRowUpdate = onRowUpdate, onRowTouch = onRowTouch, } -- Create 75 rows for i = 1,75 do local isCategory = false local rowHeight = 150 local rowColor = { default = { 1 }, over = { 1 } } -- Make some rows categories if i == 20 or i == 40 or i == 60 then isCategory = true rowHeight = 32 rowColor = { default = { 0.5 }, over = { 0.5 } } end -- Insert the row into the tableView tableView:insertRow { isCategory = isCategory, rowHeight = rowHeight, rowColor = rowColor, lineColor = { 0.5 }, params = { } } end

 

The problem is kinda complex. There are a few things fighting you here.

First, you’re loading up a table with more rows than it can display. For efficiency, a tableView destroys (culls) rows that are not on screen. When the row is brought on screen, then the onRowRender function is called. This leads to the second problem.

You’re calling network.download() which is an asynchronous call. It takes time to download that image and the image is being rendered in the networkListener function. Because network.download returns immediately, the row will render quickly, except for the image. You wouldn’t want network.download to block your UI while it’s downloading dozens of images and every time you tried to scroll an image on screen. If you scroll your tableView and a row that was on screen and still waiting on it’s image to download goes off screen, that row gets culled but the network.download() call is still active and it’s trying to render to a row that doesn’t exist.

I would consider downloading the images outside of the onRowRender function and store the images in a separate Lua table and have onRowRender check to see if the image table has a display object and display it if it does.  At a minimum you need to make sure you have a valid row before you try to insert an image into it. If the row doesn’t exist then don’t add the image to it and maybe consider removing the image. I think this would result in more network traffic than the first suggestion.  The other option is to use network download to just cache the file to system.CachesDirectory, starting the network.request() when you insert the rows and pass the expected file name to onRowRender in params and then have onRowRender just try and load the cached file.  This would likely be the most network efficient model because you can let caching work for you.

Rob

Hi Rob,

After all, thank you very much for responding quickly.

It is indeed what it seemed to me. The NetworkListener arriving while Row was unloaded because off the screen.

However, I did it that way and it worked well with the 2017 build I had. I tested in the onRowRender if the image file was already cached, and if not I performed the networkDownload.

Your solution unfortunately suits me only half, because if I start uploading images to the insertion of the Row and the Row is displayed while the image is not yet downloaded, I will have a row without image.

Anyway, I think I just solved my runtime problem by conditioning the display of the image in case of NetworkDownload with this condition:

 

if (row.\_proxy) then local img = display.newImage (row, event.response.filename, event.response.baseDirectory, 60, 40) ...... else print ("the row does not exist anymore") end

I no longer have a Runtime error and my application is working again as before.

Hoping it can help others in my case.

Thank you very much for responding to my message. Your help is always precious Rob. Have a nice day!

The problem is kinda complex. There are a few things fighting you here.

First, you’re loading up a table with more rows than it can display. For efficiency, a tableView destroys (culls) rows that are not on screen. When the row is brought on screen, then the onRowRender function is called. This leads to the second problem.

You’re calling network.download() which is an asynchronous call. It takes time to download that image and the image is being rendered in the networkListener function. Because network.download returns immediately, the row will render quickly, except for the image. You wouldn’t want network.download to block your UI while it’s downloading dozens of images and every time you tried to scroll an image on screen. If you scroll your tableView and a row that was on screen and still waiting on it’s image to download goes off screen, that row gets culled but the network.download() call is still active and it’s trying to render to a row that doesn’t exist.

I would consider downloading the images outside of the onRowRender function and store the images in a separate Lua table and have onRowRender check to see if the image table has a display object and display it if it does.  At a minimum you need to make sure you have a valid row before you try to insert an image into it. If the row doesn’t exist then don’t add the image to it and maybe consider removing the image. I think this would result in more network traffic than the first suggestion.  The other option is to use network download to just cache the file to system.CachesDirectory, starting the network.request() when you insert the rows and pass the expected file name to onRowRender in params and then have onRowRender just try and load the cached file.  This would likely be the most network efficient model because you can let caching work for you.

Rob

Hi Rob,

After all, thank you very much for responding quickly.

It is indeed what it seemed to me. The NetworkListener arriving while Row was unloaded because off the screen.

However, I did it that way and it worked well with the 2017 build I had. I tested in the onRowRender if the image file was already cached, and if not I performed the networkDownload.

Your solution unfortunately suits me only half, because if I start uploading images to the insertion of the Row and the Row is displayed while the image is not yet downloaded, I will have a row without image.

Anyway, I think I just solved my runtime problem by conditioning the display of the image in case of NetworkDownload with this condition:

 

if (row.\_proxy) then local img = display.newImage (row, event.response.filename, event.response.baseDirectory, 60, 40) ...... else print ("the row does not exist anymore") end

I no longer have a Runtime error and my application is working again as before.

Hoping it can help others in my case.

Thank you very much for responding to my message. Your help is always precious Rob. Have a nice day!