Reading and Writing Files

Overview

Reading and writing files — data in the form of plain text, JSON, XML, a local SQLite database, etc. — is essential to app development. There are countless cases when your app may need to read and/or write a file, but here are a few common instances:

Save/load user settings or other statistics.

Save the "app state" to a file and read it in a future session, so the user can return to exactly the same place where they left off, even if the app has exited.

Load data from a file that has been downloaded from a remote server and use that data in the app.

Getting File Paths

The system.pathForFile() function is the foundation of all file operations in Corona. Lua requires the entire path of a file when reading and writing, but mobile operating systems such as iOS obscure the file system via "sandboxing." This makes it difficult to determine exactly where the file resides. Fortunately, Corona has simplified this with the system.pathForFile() function which returns a path that is compatible with Lua's file I/O functions.

system.pathForFile( filename [, baseDirectory] )

Note

The baseDirectory argument is an optional constant that corresponds to the base directory where the file is located. If not specified, the default is system.ResourceDirectory. See the System Directories section below for more information.

Writing Files

In the context of this guide, "writing" is synonymous with "saving" since you might need to generate new files as well as write to existing files.

system.pathForFile() — this returns the path for the file to write, myfile.txt. Also, note that the base directory is set to system.DocumentsDirectory. This is important because, for security reasons, you can not write/save data to system.ResourceDirectory.

io.open() — this function opens the file for writing (or reading). It returns a new file handle, set to file. The second argument, "w", corresponds to the "mode" that the file will be opened in. This dictates what you'll be doing with the file. In this case, "w" indicates write and tells Corona to create (write) a new file, or overwrite the file if it already exists. For a complete list of I/O modes, see the io.open() documentation.

file:write() — assuming the file is opened in write-compatible mode, the file:write() method will write the specified string to the file handle. In this example, the contents of the saveData variable are written to the specified file.

io.close() — whenever you perform file operations and you're finished with the file handle, do not forget to call io.close(). This method closes the file handle and ends the I/O process.

Reading Files

To read a file, just get the file path, open the I/O in read mode, and set the contents to a variable.

system.pathForFile() — once again, you'll need the path to the file that you want to read from. When reading files, you can specify any of the four system directories (see System Directories below). For example, if you include/bundle data files as part of your app, you can read these files from system.ResourceDirectory.

io.open() — this function opens the file and returns the file handle, set to file in this example. This time, the second argument must be set to "r" which corresponds to the read mode. For a complete list of I/O modes, see the io.open() documentation.

file:read() — assuming the file is opened in read-compatible mode, the file:read() method will read the contents of the file and set it to the variable savedData. The argument for the read function specifies the format of the procedure. If you want to read the entire contents of the file (newline characters preserved), use "*a" as in this example. Other formats are explained in the object:read() documentation.

io.close() — as emphasized above, whenever you perform file operations and you're finished with the file handle, call io.close() to close the file handle and end the I/O process.

Reading Lines

Another common task when reading data is to process each line of a file and use it for some purpose. For example, if you have a text file representing a list of products along with a price for each, you can loop through the file by lines and output or parse each line (product) individually.

The file:lines() function, in conjunction with a for loop, accomplishes this:

This process will return the next line on each iteration and continue until no more lines are available. Each line can be used in its entirety or parsed into smaller elements using string patterns. See the String Manipulation guide for details.

System Directories

As noted above, system.pathForFile() requires a baseDirectory argument if you want to access a directory aside from the default system.ResourceDirectory.

Here are all four available system directory constants for reading/writing:

Directory

Permissions

Description

system.ResourceDirectory

read

This directory refers to the core project directory which is the same location as the main.lua file. This is the default base directory for system.pathForFile().

system.DocumentsDirectory

read/write

This directory is intended for files that the app cannot regenerate on its own, for example user-specific data, "app state" data, or anything that the app generates post-installation. Files in this directory will persist for the lifetime of the app — that is, until the app is explicitly removed from the device. On iOS, files in this location are backed up by syncing unless you specify otherwise (see notes below).

system.TemporaryDirectory

read/write

This directory is intended for single-session data. Files written to this location will generally persist as long as the app is running, but the operating system reserves the right to delete this data at any time. Thus, do not place important data in this directory.

system.CachesDirectory

read/write

Files in system.CachesDirectory tend to have a longer lifespan than those in system.TemporaryDirectory, but this is not reliable and you shouldn't place important data in this directory. On iOS, files in this location are not backed up by syncing.

Notes

On Android devices, there is no literal system.ResourceDirectory because all resource files reside inside a compressed APK file. See Android File Restrictions below for more information.

In the Corona Simulator, equivalents of system.DocumentsDirectory and system.TemporaryDirectory are located in a sandboxed folder for each application. You can view these directories and the files within by selecting File → Show Project Sandbox in the Simulator.

On iOS and macOS, the native.setSync() API can be used to set the iCloud automatic backup flag for files in system.DocumentsDirectory. See native.setSync() for more information.

When accessing system.ResourceDirectory via system.pathForFile(), setting the filename parameter to nil will return the directory path without checking if the file exists.

Accessing Files in Subfolders

You can access files in a subfolder in two ways, depending on what you want to do with the file. If you want to display an image or play a sound from the subfolder, concatenate the subfolder name with the file name and then supply the base directory. For example, if you want to display the cat.png file in the images subfolder, do the following:

Copying Files to Subfolders

The following function copies a file from one folder to another. This is useful if you need to copy a file bundled in system.ResourceDirectory to system.DocumentsDirectory. Note that you must create the destination subfolder before using this function.

Android File Restrictions

File access in Corona is based on the underlying operating system which varies by platform. On iOS devices, you can access files in all of the directories described above. On Android, however, there is no literal system.ResourceDirectory because all resource files reside inside a compressed APK file.

Corona allows direct loading of images and audio files using the appropriate APIs, but it has limited access to resource files on Android using the file I/O APIs. Specifically, the following types can not be read from the resources directory: .html, .htm, .3gp, .lua, .m4v, .mp4, .png, .jpg, and .ttf.

Because of this limitation, if you have files of these types bundled in the core directory that you need to copy to another directory, you must change the file name so it can be accessed by the file I/O APIs. For example, if you want to move cat.png from the resource directory to the documents directory, it must be renamed cat.png.txt to be copied.

Here's how to copy cat.png to the documents directory on Android, assuming it's stored as cat.png.txt. This technique works for all platforms, so if you make it work for Android, it will work everywhere.