In this chapter, you will learn how to manipulate files and directories by using the
Depending on your app's needs, storing data as a file might make more sense than storing the information in the database or in a property. Got a few details to store? Put them in the app's properties. Got a lot of structured, related data? That's what a database is for. But if you're storing binary data, such as images, we recommend you store it in files. Fortunately, Titanium makes it simple to perform basic filesystem CRUD operations.
Let's start by examining the modules available to you:
- Ti.Filesystem is the top level Filesystem module. You use it to create files and directories, and to get handles to existing files (which you'll then use to read from or write to those files). This module also contains methods that let you determine if storage space is available or if external (SD card) storage is present. With
Ti.Filesystem, you can also obtain handles to the various directories accessible by your application.
- Ti.Filesystem.File is the file-level object, which includes properties and methods that support common filesystem based operations such as reading, writing, and more.
- Ti.Filesystem.FileStream is a wrapper around Ti.Filesystem.File that implements a Ti.IOStream interface
A few of the ways you might use the filesystem include:
- Accessing files that ship with your application
- Saving data your app downloads from a web service
- Saving data generated by the user to a file, which you might access later or upload to a service
- And novel uses, such as saving the contents and state of a view as a blob in a file
Before we get into the mechanics of accessing the file system, let's talk about where on the device you can access files. The following locations are potentially accessible:
- Ti.Filesystem.applicationDataDirectory: A read/write directory accessible by your app. Place your application-specific files in this directory. The contents of this directory persist until you remove the files or until the user uninstalls the application.
- Ti.Filesystem.resourcesDirectory: A read-only directory where your application resources are located; this directory corresponds to the project/Resources directory in Studio. The contents of this directory persist until the user uninstalls the application.
- Ti.Filesystem.tempDirectory: A read-write directory where your application can place temporary files. The contents of this directory persist until your application fully closes, at which time the operating system could delete your files.
- Ti.Filesystem.externalStorageDirectory: A read-write directory on the external storage device (SD card) accessible by your app, if such a location exists. Check first with
Ti.Filesystem.isExternalStoragePresent()(which returns a Boolean). Available only for the Android and Windows platforms.
- Ti.Filesystem.applicationCacheDirectory: A read-write directory where your application can cache data. The contents of this directory persist after your application fully closes but at the discretion of the operating system.
- For the Android platform, the cache quota will change over time dependent on the app usage, and system wide storage, the data will be persisted until disk space is required.
- For the iOS platform, there is no size limit but the data only remains there until iOS cleans the directory if it requires the disk space.
- For the Windows platform, there is not size limit, and the data stored here will be ignored when a users backs up their storage to the cloud.
In the upcoming sections, we'll look at the various ways you can interact with files. We'll start with the most basic, which is simply getting a reference to the file. Then we'll move on to reading, writing, and 'rithmetic, er, other operations.
Getting a file handle
To work with a file, you need a reference to it, otherwise known as a handle. You do this with the Ti.Filesystem.getFile() method, passing to it the path to and name of the file. What you get back is an instance of the Ti.Filesystem.File object. For example:
Again, writing to files is straightforward. Get a handle, call
JSON.stringify() first. Later, you can read in the file and rehydrate the object with
Reading from a file is simple enough: get a handle then call the
read() method. Keep in mind that the
read() method returns the contents of the file as a blob. That's great if your file contains binary data. But if you're working with a text file, grab the
text property of that blob to get the plain text contents of the file. Or, you can use the
mimeType property to determine the file's MIME type. Like this:
To append the the file use the append() method, this takes a string, a Blob or a Ti.Filesystem.File object
Creating and copying
Titanium makes it pretty easy to create a file. Grab a file handle, then write to the file. If it doesn't already exist, Titanium will create it for you. There are some specific methods you can use if you want to explicitly create the files. But you don't need to.
On Android and Windows there is a specific copy method, it can be used like the below
iOS doesn't have a copy method so you can copy a file like below.
Renaming files follows the same format as above: get a handle, do the operation. But, we need to keep in mind how the file handles are, er, handled. After renaming the file, our file handle will still point to the old name. If you expect it to be automatically updated to point to the new file name, you could be in for some unexpected behaviour. To demonstrate
rename() and this file handle behavior, the following code example previews directories and how you can output a directory listing.
We'll end our discussion of file with a look at deleting files. As before, grab a handle and do the operation. The
deleteFile() returns a Boolean value indicating whether the operation succeed. This means it won't throw an error if the file doesn't exist or is read-only. You'll just get
We looked at how to list the files in a directory in the preceding section on renaming files. In this section, we'll look at how to create directories, delete directories, and move files into directories. Since the operations are pretty straightforward at this point, we'll do it all in one example:
Case Sensitivity Note
Transitioning from case-insensitive filesystems, such as FAT32, NTFS and HFS+, to case-sensitive filesystems on Android and Mobile Web devices means that a file name referenced in the source code may not match the case of the file on the device's filesystem. For example, an application may work on the Android emulator but may not work on an Android device, throwing a runtime error or not displaying an image. It is recommended to lowercase all file names. If you change the name of a file, clean your project's build directory before building the application.
In this activity, you will update the local data sample app you worked on in the 5.2 and 5.3 tutorials to save the weather icons to the local filesystem.
This activity builds upon the app you wrote in sections 5.2 and 5.3. If you don't have a working version of the localdata app representing the end-state of the 5.3 activity, grab this starting point code.
- If necessary, download the starting point code, extract the zip, and import the project.
- In app.js, add the filesystem code necessary to create an 'icons' directory within the app's data directory. You should check that the directory exists and create it only if necessary. You'll store cached icon image files in this directory.
- Add a
myapp.getImage()function that accepts the icon's name as a string. This function will return either a cached image file from the filesystem or load the image from the remote URL and cache it for future use. Your function should implement this general logic:
- Add liberal logging statements with
Ti.API.info()so that you can monitor the actions of your function.
- Declare a file handle that points to the icon file in the icons directory.
- If the cached image file exists, return its native path.
- Otherwise, cache the image but return the icon's absolute URL by implementing this logic:
- Create an ImageView that loads the image from the remote URL, which would be http://www.worldweather.org/img_cartoon/ plus the icon's name.
- Because it will take a few seconds to download that image, use
setTimeout()to wait 5 seconds. Then, write the results of imageView
.toImage()to the file. A successfully retrieved image will be 35 pixels wide. Any other width indicates an error. Cache only successfully retrieved files.
- To account for the delay in loading and caching, return the icon's absolute URL so that an image is displayed the first time the app is run.
- Add liberal logging statements with
- Update the image object in the table so that its
imageproperty is set to the return value of your
- Build and test the app. Check the logs to confirm that the function loads images from the cache (assuming you logged out appropriate info).
You should notice a momentary pause the first time you run the app. That's because the icon is being loaded from the remote URL. Close the simulator/emulator, launch it again and open your app. The icons should be displayed immediately because they are read from the filesystem this time. If you're testing in the iOS simulator, you can view the icons directory by opening this path: yourHomeDirectory/Library/Application Support/iPhoneSimulator/version/Applications/guid/Documents/icons. You can determine that guid by logging out the iconsfolder
References and Further Reading
In this chapter, you learned how to manipulate files and directories by using the
Titanium.Filesystem module. You put that knowledge to work by implementing image caching in an app through which remote images are saved to the filesystem for later retrieval.