Corona’s Class Events

Implementing OOP in Lua and using Corona’s custom events has turbo-charged my development.

Lua doesn’t have OOP built in like some languages, but there are a couple of methods that can be used to get a version working. My preferred method is to use the “define a class using a function” approach. I’m not after full-on OOP with inheritance and all that jazz, but my version of this method gives me just what I need: code re-use and the syntactic sugar of the dot notation. I don’t ask for much really. The OOP code I use here was derived from the good work of people in the Corona Labs community, and also from Jonathan Beebe’s blogs on the subject.

For example, to define a timer class I would use something like the following, which resides in its own Lua file called timer.lua:

-- Table object returned by this Module - hence use of M for Module
-- This table is used to contain all Class definitions
local M = {}
local function myTimer(pDuration, pTimerName)
local mObj = {} -- Table to store class methods etc
-- Internal methods
local function tick()
end
-- Public methods
local function timerName()
end
mObj.timerName = timerName
local function start()
end
mObj.start = start
local function isPaused()
end
mObj.isPaused = isPaused
local function pause()
end
mObj.pause = pause
local function resume()
end
mObj.resume = resume
local function destroyObject()
end
mObj.destroyObject = destroyObject
return mObj -- Return the class definition
end
M.myTimer = myTimer -- Assign the myTimer function to the M table
-- Return the table with all Class definitions
return M

The methods would obviously be filled in with the required code, this is just a cut down version to show how I define my classes.  Now that I have my class, I can create instances of it in my code as follows:

local myTimers = require("timer")
local aTimer = myTimers.myTimer(60, "A Timer")
aTimer.start() -- This will start the timer
aTimer.pause() -- This will pause the timer
aTimer.resume() -- This will resume the timer
aTimer.destroyObject() -- This will destroy the timer and free up display objects etc

And if I wanted a group of timers, which MyFitnessTimer will need, then I can just use Lua’s tables:

local myTimers = require("timer")
local theTimers = {}
for x = 1, 10, 1 do
 theTimers[x] = myTimers.myTimer(x * 10, "Timer " .. x)
end

And there I have a collection of 10 timers all ready for use. I sometimes create a separate class – I call them collection classes – to hold groups like this. It can be useful to have a collection class as you can put methods in the collection to cater for the retrieval of groups of objects. For example, my collection class usually has an “open” method, which will query the database for the required records and then create objects for each record, storing these in a Lua table. The collection class then has methods to allow me to access the table. I store the collection class in the same timer.lua file as the class that it’s a collection of. I’d implement a collection class something like the following:

local function myTimerCollection()
local mObj = {} -- Table to store this class
local mCol = {} -- Table to store the myTimer objects
local mItemIndex = 0 -- Index into the mCol table
local function init()
end
local function add(obj)
end
mObj.add = add
local function item(index)
end
mObj.item = item
local function count()
end
mObj.count = count
local function open()
init()
-- Get records from DB
-- Iterate through records
-- Add each record to the mCol table as a myTimer object
end
M.myTimerCollection = myTimerCollection

If I wanted to get a group of myTimer objects using the collection, I would write code something like this:

local myTimers = require("timer")
local timerCollection = myTimers.myTimerCollection()
timerCollection.open() -- This gets my records from the database and creates a collection of them, i.e. a Lua table
for x = 1, timerCollection.count(), 1 do
print(timerCollection.item(x).timerName() )
end

With all that code in place I find it really straightforward to write reusable code with at least a semblance of OOP. As you can probably tell, I like to hide code away and this is my way of doing so with Corona and Lua.

The next step was to figure out how to have my objects respond to taps and swipes. There are two ways of doing this sort of thing. The first way is to have the class itself handle the taps. This can be achieved by having event listeners defined within the class. The second way is to have the code that is using the class set up event listeners against the class. My preference is to include all relevant functionality in the class itself, but only where that makes sense to do.

For example, MyFitnessTimer makes use of on-screen boxes representing myTimer objects that must respond to taps, double taps, and swipes. But the functionality of each may affect the object and/or the other objects on screen. As an object doesn’t know about other objects, not all code can be written into the class. If a swipe means delete, then the class can contain code to remove the database record and tidy up the display object, but what about the remaining boxes? Do they need reformatting on-screen? This is where Corona’s custom events came into play, and they’re a treat.

Firstly, I had to write some code into a new class that would handle tap, double tap, and swipe. I setup a new class called timerBox:

local function timerBox(pGroup, pXPos, pYPos, pTimerName, pSeqNo)
 local mObj = {}

 -- Internals
 local mTimerName = pTimerName
 local mSeqNo = pSeqNo
 local mPreviousTapTime = 0 -- Stores the time a successful tap occurred
 local mDoubleTapDelay = 200 -- ms that a Double Tap can occur in
 local mSingleTapTimer = nil -- Timer for use with the touch listener
 local mSwipeXLength = 25 -- Pixel length for a swipe to register
 -- UI
 local mBox = display.newRect(pXPos, pYPos, 90, 50)
 pGroup:insert(mBox)
local function getObjectDetails()
local myEventTable = {timerName = mTimerName, seqNo = mSeqNo}
return myEventTable
end
local function fireSingleTapEvent()
 -- Raise a Single Tap event 
 local event = { name = "myTap", target = getObjectDetails() }
 Runtime:dispatchEvent( event )

 mBox:setFillColor(255, 0, 0) -- Local code that always happens on a Tap

 end
 local function touch(event)

 if event.phase == "began" then
 -- Set Focus to object
 display.getCurrentStage():setFocus(event.target)
 end

 if event.phase == "ended" then

 -- Check if it's a Swipe first
 local xDiff = event.xStart - event.x
 if xDiff < 0 then
 xDiff = xDiff * -1
 end
 if xDiff < mSwipeXLength then
 local tapTime = system.getTimer()
 if tapTime - mPreviousTapTime > mDoubleTapDelay then
 -- Raise Single Tap Event in mDoubleTapDelay ms
 mSingleTapTimer = timer.performWithDelay(mDoubleTapDelay + 1, fireSingleTapEvent)
 else
 -- Cancel Timer if active so we don't get two tap events firing
 if mSingleTapTimer ~= nil then
 timer.cancel(mSingleTapTimer)
 mSingleTapTimer = nil
 end

 -- Raise a Double Tap event 
 local event = { name = "myDoubleTap", target = getObjectDetails() }
 Runtime:dispatchEvent( event )
 end

 -- Remember time of this tap so we can check for Double Tap next time
 mPreviousTapTime = system.getTimer()
else
 -- Cancel Timer if active so we don't get two tap events firing
 if mSingleTapTimer ~= nil then
 timer.cancel(mSingleTapTimer)
 mSingleTapTimer = nil
 end
 -- Raise a Swipe event 
 local event = { name = "mySwipe", target = getObjectDetails() }
 Runtime:dispatchEvent( event )

 end
 display.getCurrentStage():setFocus(nil)

 end

 return true

 end
local function destroyObject()
 mBox:removeEventListener("touch", touch)
 mBox:removeSelf()
 mBox = nil
 end
 mObj.destroyObject = destroyObject
 mBox:addEventListener("touch", touch)
 return mObj
end
M.timerBox = timerBox

The important parts to note are that the event listener is defined within the class, thus saving that sort of code cluttering up other areas, and that I am using Corona’s custom events to let whatever is using the class know when taps occur. In order to trap these events, you need some code like the following in your main Lua file:

local myTimers = require("timer")
local myBox = myTimers.timerBox(mainGroup, 100, 100)
local onBoxTap(event)
-- Respond to single tap
end
local onBoxDoubleTap(event)
-- Respond to double tap
end
local onBoxSwipe(event)
-- Respond to swipe
end
-- Listen out for the tap events
Runtime:addEventListener("myTap", onBoxTap)
Runtime:addEventListener("myDoubleTap", onBoxDoubleTap)
Runtime:addEventListener("mySwipe", onBoxSwipe)

The code above shows how I implement a form of OOP with Corona and Lua in order to create reusable code. I’ve also shown how I use Corona’s custom events to let code that uses my classes know when taps and swipes occur. With this framework in place it’s now very easy for me to create the main user interface parts of my app. I also think this approach makes it easier for me produce better code as maintenance is less difficult given that most implementation details are in separate classes.

If you’ve got any thoughts about my approach I’d be happy to hear them and I hope I’ve helped some of you with your own coding.

%d bloggers like this: