Something that’s becoming increasingly popular is the ability to “pick up where you left off” or stop and resume with hardly any friction. More and more, apps are starting off exactly how you left them—and that doesn’t exclude mobile apps (in fact, mobile is arguably what inspired this behavior).
Having your apps start off where your user left them is also known as “state saving”, and that’s exactly what I’m going to cover in this tutorial. As with any tutorial that’s posted here, I encourage you to take what you learn and build upon or modify the code to suit the specific needs of your app.
Resuming Suspended Apps
By default, when your app is “suspended” (e.g. the user presses the home button on their device), your app doesn’t actually quit, it goes into a suspended state and will automatically resume where the user left off. Your app will eventually quit if the user opens too many apps before returning to your app (the operating system will close it).
In case you need to disable the default behavior for whatever reason, for iOS apps you can do so by setting UIApplicationExitsOnSuspend to true in build.settings. The default is false (recommended), which means your apps will resume where they left off automatically (unless the OS quits the app in the background).
iphone = {
plist = {
UIApplicationExitsOnSuspend = true
}
}
Most of the time when the user leaves your app, it’s voluntary, but sometimes it isn’t (e.g. they receive a phone call), so it’s up to you to ensure that the on-resume behavior of your app isn’t going to ruin the user’s experience.
A perfect example of this is gameplay. If you take Doodle Jump for example, if you leave the app, when you return to the app, the game is magically on the pause screen. If you ended a phone call and suddenly your character was already jumping around mid-air, my guess is that you probably wouldn’t be beating your high score in that round. Resuming to a pause screen is much better as far as user-experience is concerned.
Thankfully, Corona dispatches system events when your app is suspended and resumed. Here’s an example of how you might hook into these events to pause your game when your app is suspended:
local function onSystemEvent( event )
if (event.type == "applicationSuspend") then
pause_game()
end
end
Runtime:addEventListener( "system", onSystemEvent )
It depends entirely on your app what the pause_game() function does, but you get the picture. You add an event listener to the global Runtime object, and specify the function that will be called when the event is called.
Then, we simply check for an event.type of “applicationSuspend” and the subsequent block of code is what’s ultimately executed when your app is suspended.
Resuming from a “cold” start
This scenario is a little more difficult. When your app launches cold (e.g. it was not suspended, it is launching from a closed/quit state), the default behavior is to obviously start your app from the beginning (main.lua).
The first thing you need to do is to “save” the current state of your app in some way. You can choose whichever way you want, but I recommend storing data in a table, converting it into a JSON string, and then saving the string to a file for later loading (see: Reading and Writing Files in Corona).
Next, you need to load that file and tell your app what to do with the data when your app opens. If done correctly, your users will never be able to tell if your app resumed from a suspended or a cold state.
For saving, you’ll want to check for the “applicationExit” event.type in the system event listener we set up a little while ago. Then, you’ll make use of the “applicationOpen” event.type to load the saved data and “put things back together” for your user. Here’s an example:
local function onSystemEvent( event )
if (event.type == "applicationExit") then
save_state()
elseif (event.type == "applicationOpen") then
load_saved_state()
elseif (event.type == "applicationSuspend") then
pause_game()
end
end
Runtime:addEventListener( "system", onSystemEvent )
The example may seem overly simplistic, but that’s how simple it is.
I won’t spend a lot of time going through the details of the contents of the save_state() or the load_saved_state() functions, because that’s going to very different for each app. At minimum, especially in games, you’ll want to save the kinds of objects on the screen, their properties, and their on-screen locations (so they can be recreated and repositioned in the load_saved_state() function).
More and more, users are expecting to be able to “come and go” from their apps whenever they want, and if you want to provide your users with the best possible experience, you should try your best to make app resuming from a suspended or cold state completely indistinguishable from your user’s perspective.




Mo
Nice and timely tutorial!
I am curious about one thing. In the code above:
———-
local function onSystemEvent( event )
if (event.type == “applicationSuspend”) then
pause_game()
end
end
Runtime:addEventListener( “system”, onSystemEvent )
———
pause_game() is probably local to the module where the code is placed. But what about other modules if I do not want to have pause_game() as a global function (not only because it will be global but because many items in that function will be themselves local to a specific module) So my questions is:
Can I put the code above on each module where I need to deal with resume/suspend action. For instance:
– gameScreen.lua where my main game loop is
- options.lua where the user changes game options and I want to make sure the options are saved if the app is suspended while on running that module
In other modules, I won’t need to worry about that issue like help.lua and score.lua
OR
Do can I only put that code in the main.lua?
Thanks again for these incredible tutorials Jon.
Mo.
ps: Anyway, at all, the next tutorial be about IAP? please, pretty please:)
Jerry Adkins
In my options.lua I save states to a database table as buttons are plressed, or checkboxes are selected.
owenyang
nice post!
Matt
After being suspended, if the app is later quit force ably by the os for being inactive/unused for too long, will the exit event be fired?
Fred
Thank you!