Creating an RSS and JSON driven app with Corona SDK

April 26, 2011 by Haakon Langaas Lageng

While working on the Nyhetene app, an app that gathers more than 100 different rss feeds, we faced a few challenges that needed to be solved. Our biggest issues were related to loading external data, like rss feeds and json data, loading remote images, and displaying the results in a proper manner. We have solved problems related to parsing non US characters and the app crashing when asynch image loading returns. Monkeybin believes in clean, readable code, where logic is separated from display, so this series will follow some trusted OOP patterns to achieve that.

This post is aimed at developers using Corona SDK to build apps. All our code is object oriented, which (should) make it easy to take advantage of our classes in your own apps.

In this post, we will

  • Put together the building blocks for our app
  • Implement third party libraries like director, xmlparser and middleclass
  • Load an RSS feed
  • Parse the RSS feed
  • Display the RSS feed

DOWNLOAD SOURCE CODE

Getting started

I start out with a basic setup, using director to manage the views. I have created a View_News.lua file, which will act as the main view. I renamed the director file to Lib_Director, and I have changed line 165 in the director file to function Lib_Director:changeScene(…) to make it work.

We have spent a great deal of time discussing and testing different ways to get a clean project root. Since Corona SDK don’t work with lua files inside folders, we have started to use an underscore convention file naming. It gives us a natural grouping of files and functionality, as you’ll see soon.

Our projects often contains a third party library called middleclass.lua. It brings some missing OOP features to the table. I have renamed the file to Lib_MiddleClass.lua. With middleclass loaded, we are able to create classes and objects, have private variables and functions etc. It took me some 3 minutes to get to know the library, well worth the time invested!

This particular project also needs an XmlParser, and I have used one I found at lua-users.org. I had to modify it to make it work with prefixed element names, and I added some utility functionality for extracting attribute values ++. You’ll find my version in the source code.

I will start out with the classes and parsing functionality. First out, I create a class to handle loading feeds:

Data_UrlLoader.lua

UrlLoader = class("UrlLoader")

UrlLoader.callbackHandler = nil
UrlLoader.networkListener = nil

function UrlLoader:initialize(url, callback)
	UrlLoader.callbackHandler = callback
	network.request(url, "GET", UrlLoader.networkListener)
end

UrlLoader.networkListener = function(event)
	if event.isError then
		native.showAlert("Network Error", event.response, {"OK"})
	else
		UrlLoader.callbackHandler(event.response)	
	end
end

A couple of things happens here. The first line is a Lib_MiddleClass function. It creates the object for us. The next two lines are MiddleClass’ way of defining private variabels. These variables will only be available to the UrlLoader class and cannot be reached by any other class in our app.

The function UrlLoader:initialize is also a special MiddleClass thingy. When you “new” this object, MiddleClass internally calls the initialize method. So if I were to create an instance of UrlLoader in my View_News.lua, I’d write UrlLoader:new(). Behind the scenes, MiddleClass calls the initialize method. That means the initialize method is actually the constructor, if you are familiar with that term.

The UrlLoader contructor takes two arguments. The first one, url, is the absolute url to the rss feed we want to load. The second argument is a delegate method. Since loading data with network.request is asynchronous, we must have a way of telling the parent class, in our case View_News, when the loading is completed, and return the loaded result.

Rss_SyndicationFeed.lua

SyndicationFeed = class("SyndicationFeed")

SyndicationFeed.items = nil
SyndicationFeed.title = nil

function SyndicationFeed:initialize(title)
	SyndicationFeed.title = title
	SyndicationFeed.items = {}
end

function SyndicationFeed:addItem(item)
	table.insert(SyndicationFeed.items, item)
end

function SyndicationFeed:parseFeed(xml)
	local parsed = XmlParser:ParseXmlText(xml)
	local channel = XmlParser:XmlNodes(parsed, "channel")
	local syndicationFeed = SyndicationFeed:new("NRK")
	
	for i, xmlNode in ipairs(channel) do
		if xmlNode.Name == "item" then
			local item = SyndicationItem:new(xmlNode)
			SyndicationFeed:addItem(item)
		end
	end
end

function SyndicationFeed:getItems()
	return SyndicationFeed.items
end

function SyndicationFeed:getItem(i)
	return SyndicationFeed.items[i]
end

The SyndicationFeed class has a method for parsing xml to a SyndicationFeed, parseFeed(). It will traverse the xml file, loop through all the items, and build a table of SyndicationItem objects for us to use in the application. The data comes in as a raw string, so the first thing to do is to crunch it through the XmlParser:ParseXmlText method to create an xml object that we can navigate and extract data from.

Remember that the channel element in an rss xml contains a bunch of item elements? Putting the channel’s children in a variable give us something to loop through. Then I create an instance of my custom object SyndicationFeed. At this point, I am ready to loop through the items inside the channel variable.

Everytime the loop finds an xml element that is named “item”, I am creating a new SyndicationItem instance, and pass in the xml item.

Rss_SyndicationItem.lua

SyndicationItem = class("SyndicationItem")

function SyndicationItem:initialize(node)
	self.title = XmlParser:XmlValue(node, "title")
	self.link = XmlParser:XmlValue(node, "link")
	self.description = XmlParser:XmlValue(node, "description")
	self.updatedDate = XmlParser:XmlValue(node, "a10:updated")
	
	local enclosure = XmlParser:XmlAttributes(node, "enclosure")
	if enclosure then self.enclosureUrl = enclosure.url end
end

The SyndicationItem is a lightweight object, taking care of it’s own extracting of data from the rss feed. It uses the XmlParser to assemble a title, link, description, updatedDate and image link – if it exists.

View_News.lua

module(..., package.seeall)

function new()
	local g = display.newGroup()
	
	local callback = function(rss)
		local syndication = SyndicationFeed:new("NRK Feed")
		syndication:parseFeed(rss)

		local items = syndication:getItems()
		for i=1, #items do
			local item = items[i]
			local itemGroup = display.newGroup()
			local title = display.newText(item.title, 10, 10, "Helvetica", 16)
			itemGroup:insert(title)
			itemGroup.y = 10 + (40 * (i-1))
			g:insert(itemGroup)
		end
	end
	
	UrlLoader:new("http://www.nrk.no/nyheter/siste.rss", callback)
	
	return g
end

Taking a look at View_News, you’ll see that a couple of things are going on. If you look down at the lower part of the code block, you see that I am instantiating the UrlLoader class from before, passing in the feed url and a callback delegate method. A few lines up, you find the delegate method itself. That method is being invoked when the UrlLoader has finished loading the feed! When that happens, a SyndicationFeed object is created and the feed gets parsed to SyndicationItems. Next in line is the logic to show the result on the device. I simply loop through the table of SyndicationItems inside the SyndicationFeed object, and writes their titles on the screen.

main.lua

display.setStatusBar( display.HiddenStatusBar )

-- Libraries
local director = require("Lib_Director")
require("Lib_MiddleClass")
require("Lib_XmlParser")

-- Classes
require("Data_UrlLoader")
require("Rss_SyndicationFeed")
require("Rss_SyndicationItem")

local mainGroup

local function main()
	mainGroup = display.newGroup()
	mainGroup:insert(director.directorView)
	director:changeScene("View_News")
	return true
end

main()

Finally, I’m sewing it all together in the main.lua file. I load in the libraries and the classes, and use director to switch to the View_News view. And that’s it! Hope you’ve enjoyed the tutorial, and please check back for more as I continue to build out the app.

Disclaimer. I know I could have, and probably should have, separated stuff even more. Putting the parsing of xml inside the SyndicationFeed and SyndicationItem is not the “correct” way of doing it. But I don’t need any extra separation of concerns. This is all I need. YAGNI and KISS, two of my favourite programming principles weighs heavily on the choices I make.

DOWNLOAD SOURCE CODE

Share
Corona SDK, Lua, and Programming

Add New Comment

image description
  1. Vuda
    Vuda May 17, 2013

    Hi Haakon,
    When i run this demo source code , i got some problems with latest corona version (CoronaSDK-2013.1076)
    – Anti-aliasing is not supported and has been disable
    – \Lib_XMLParser.lua:80: XMLparser: trying to close e width head
    – Your rss link is a die link. ( i try to change this link)
    Can you help me look at them?

  2. asa
    asa November 13, 2012

    It works fine for me. Thanks

    But I have a question, isn’t there any way to just download the latest 10 RSS feeds instead of downloading the whole xml file?

    because the site which I’m dealing with has more than 2000 feeds and it take long time to download it on the device.

  3. Remi
    Remi December 7, 2011

    This code doesnt work with Corona SDK (version 377 – last one supports ARMv6 processors). I’m getting no errors, just blackscreen :) Any ideas how to make it work for this specific version of Corona?

    PS. On the newest version of corona(ver. 591 for ARMv7 only) it’s working good, but i need it for older devices (ARMv6)

  4. Charlie
    Charlie October 17, 2011

    I am using your RSS feed to read an XML file that is generated forma database query.How can I get the screen to scroll if there is more than one screen worth of data?

    I am trying to implement “local myList = tableView.newList” but am running into snags.

    Any ideas?

    thanks

  5. Haakon
    Haakon August 9, 2011

    Hi Kevin,

    I just checked the code here, and it works fine with the latest version of Corona. I suspect that you have changed the feed url, and are encountering new parsing problems that is not handled by the XML Parser. We have recently created an app that handles more than 20 different interpretations of the RSS Spec, but that parser is quite heavy weight… You may contact me directly by e-mail using my first name at monkeybin.no if you need more help with the source code.

  6. Kevin
    Kevin August 3, 2011

    Hi Haakon,

    this still doesnt work in the newer versions of Corona :( Did you find any solutions?

  7. NexGen Group
    NexGen Group July 5, 2011

    Great stuff here, but I do have one question: for your “description” pull, what if it is filled with a bunch of attributes and you need to pull specific within this, specifically this item:

    a href=“http://your_link.com”
    src=“http://image_link.com/default.jpg”

    from this :

    ……<div style=“color: #000000;font-family: Arial, Helvetica, sans-serif; font-size:12px; font-size: 12px; width: 555px;”>
    <table cellspacing=“0” cellpadding=“0” border=“0”><tbody><tr><td width=“140” valign=“top” rowspan=“2”><div style=“border: 1px solid #999999; margin: 0px 10px 5px 0px;”><a href=“http://your_link.com”><img alt="" src=“http://image_link.com/default.jpg”></a></div></td>…….

  8. Haakon
    Haakon June 19, 2011

    Hi Kevin, glad it helped you.

    I will take a look at the new Corona Build in a day or two, wonder what they have done now…

  9. Kevin
    Kevin June 17, 2011

    great tutorial!! worked like a charme and helped me a lot! but since i upgraded to a newer build of corona (2011.540) i get the following error:

    Runtime error
    …xx/Lib_XmlParser.lua:161: attempt to index local ‘xmlTree’ (a nil value)
    stack traceback:
    ©: ?
    …xx/Lib_XmlParser.lua:161: in function ‘XmlNodes’
    …xx/Rss_SyndicationFeed.lua:17: in function ‘parseFeed’
    …xx/View_Partyshots.lua:43: in function ‘callbackHandler’
    …xx/Data_UrlLoader.lua:15: in function
    <…xx/Data_UrlLoader.lua:11>

    any suggestions? thanks!

    Kevin

  10. Robert de Boer
    Robert de Boer April 28, 2011

    Works fine now! Thanks!

  11. Haakon Langaas Lageng
    Haakon Langaas Lageng April 28, 2011

    @Jon: Thanks for the tip. I have added a config.lua to the source code.

  12. Jon
    Jon April 28, 2011

    The new version works a treat on windows – although as there is no config.lua file in the archive you have to view as a “MyTouch” to get a simulator build with the correct sized screen.

    Thanks for posting this as it’s really helpful and I’m looking forward to part 2!

  13. Haakon Langaas Lageng
    Haakon Langaas Lageng April 28, 2011

    @Alberto Fonseca: MiddleClass is great. Their documentation says it all: https://github.com/kikito/middleclass/blob/master/README.textile. If you need more functionality, extend it with middleclass-extras. As for bugs and memory leaks, we haven’t experienced any trouble at all. We use an XmlParser from lua-users.org. I have linked to it in the blog post, but would recommend that you check out our modified version and how we use it in the app. Really simple.

    As for performance… that is a blog post of it’s own. My conclusion on the matter would be: Who the fuck cares? Lua code is insanely fast to run. Why does it matter if running a few lines of code takes 3 or 7 milliseconds? The user will not notice. So I have stopped worrying about performance in our apps, and focus mainly on making something that does not leak or bug, and with as little texture memory usage as possible. That is where the bottleneck on todays devices lay…

  14. Alberto Fonseca
    Alberto Fonseca April 28, 2011

    Great writeup! In particular I like the clean OO coding style using Lua. Can you share your experience with MiddleClass? Is it pretty reliable in it’s current state? I’m looking to try something like this as long as it’s not too large/slow or introduces bugs into our code (memory leaks, etc).

    Also, which library are you using to parse your XML? I’ve been looking for an easy to use parser to work with Corona.

  15. Haakon Langaas Lageng
    Haakon Langaas Lageng April 27, 2011

    @Robert de Boer: Robert, try the new source code and see if it works. Let me know. Haakon

  16. Haakon Langaas Lageng
    Haakon Langaas Lageng April 27, 2011

    @Ivan: Hi, I just updated the project with correct source files and also changed the naming to work with windows. Thanks for your feedback! Haakon

  17. Robert de Boer
    Robert de Boer April 27, 2011

    Tried to run the sample project, gives no errors, but doesn’t show anything except for a black screen?

  18. Ivan
    Ivan April 27, 2011

    Hey!, good stuff. Keep up the good work!

    Note: It seems that the zipped source code and the blog content are different in many ways. Also I got a bunch of errors on Windows because the dots on the files, I guess it’s a bug of Corona SDK anyway.

    I can’t wait to see the part 2!