Saving data to a file in your Android application

This is the second post in my series about storage in Android applications. The other post is available here :

http://blog.cindypotvin.com/introduction-how-to-save-data-in-your-android-application/

This post is about saving to a file from an Android application, which is the easiest way to store data. There are many situations where you may need to save a file : you may want to use an existing file format to create files that can be opened by the user in another application or the data is simple enough that it can be represented by a text file or a format like XML or YAML. For complex data a database may be a better option, since accessing and parsing a large file can be slow and there are no integrity checks unless you code them by hand. On the other hand, there is less overhead and it easier to work with files than debugging with the data in a database. Depending on how the user will interact (or not) with your files, you will need to decide first which kind of storage to use.

Subscribe to my newsletter to get an executable code sample for reading/writing to a file with the Android SDK.

Powered by ConvertKit

Internal storage

Each application has its own private internal storage to save files. This is the kind of storage to use if the user shouldn’t be able to modify the file from outside your application, and if other application shouldn’t be able to access those files. Since the internal storage is private to your application, the files will be deleted if your application is uninstalled. The internal storage is also where your application is installed by default, so your files will always be available. On some older or cheaper devices the internal storage is quite limited, so you need to be careful about the size of the data you save if you need to support those devices.

You should never hardcode the path to the storage directories, since the directory may changes depending on the version of the Android OS used.  Also, Android 4.4 introduces the concept of multiple users : in that case, the internal and external storage depend on the user logged in and the files of the other users will be invisible. Here are some of the methods used to get the paths to the internal storage:

  • android.content.Context.getFilesDir(): returns a java.io.File object representing the root directory of the internal storage for your application from the current context.
  • android.content.Context.getDir(String name, Context.MODE_PRIVATE): returns a java.io.File object representing the directory name in the internal storage, creating the directory if it does not exists. The second parameter can also be used to set the directory to MODE_WORLD_READABLE or MODE_WORLD_WRITABLE so it is visible by all the other applications, but this is is risky security-wise and was deprecated in API level 17 (Android 4.2).
  • android.content.Context.getCacheDir(): returns a java.io.File object representing the internal cache directory for the application. This is mean for small files (the documentation suggests no more that 1MB total) that can be deleted at any time when the system needs more storage. There is no guarantee that the cache will be cleared, so you must also clear those files manually when they are not needed anymore.

As you can see, the files are represented by the File object from the java.io namepace: there is no file object specific to the Android SDK and the standard Java APIs for reading and writing files are used. Also, there is no specific application permission to set in the Android manifest to use the internal storage since it is already private to the application.

External storage

In addition of the internal storage, there is an external storage space shared by all the applications that is kept when your application is uninstalled. This is the storage that is shown when using a file explorer application and when the device is plugged in your computer. It may be implemented as a SD card that can be removed or as a partition of the built-in storage in the device, so your application should be able to work even if the card is removed or changed. To check the current state of the external storage, you can call the getExternalStorageState() method.

On device with many users (starting with Android 4.4), the external storage is specific to the current user and files for other users can’t be accessed. Also, there may be more than one external storage if the device has a built-in external storage which is a partition on the internal memory and a SD card: in that case, the built-in storage is the primary external storage. Reading files from the external storage requires the READ_EXTERNAL_STORAGE permission and writing or reading files requires the WRITE_EXTERNAL_STORAGE permission.

Here are the methods you should use to call to get the directories of the primary external storage:

  • android.os.Environment.getExternalStorageDirectory(): returns a java.io.File object representing the root directory of the primary external storage of the device that is shared by all applications.
  • android.os.Environment.getExternalStoragePublicDirectory(): returns a java.io.File object representing a public directory for files of a particular type on the primary external storage of the device.  For example, you can get the path to the public music directory by calling Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC) or the public pictures directory by calling Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).
  • android.content.Context.getExternalFilesDir(): returns a java.io.File representing the root directory of the primary external storage specific to your application, which is under the directory returned by getExternalStorageDirectory(). Unlike the other directories of the external storage,  the files you store in that folder will be deleted when your application is uninstalled. So, if you need to store files that are only needed by your application you should use this folder. Also, there is no specific permission needed for the application to read or write to its own external storage starting with Android 4.4, but with older versions your application needs the READ_EXTERNAL_STORAGE or WRITE_EXTERNAL_STORAGE permission.
  • android.content.Context.getExternalFilesDirs(): returns an array of java.io.File representing the root directories of all the external storage directories that can be used by your application with the primary external storage as the first directory in the array. All those directories works the same as the primary storage returned by the getExternalFilesDir() method. If the device has a built-in storage as the primary external storage and a SD card as a secondary external storage, this is the only way to get the path to the SD card. This method was introduced in Android 4.4, before that it was impossible to get the path to the  secondary storage.
  • android.content.Context.getExternalCacheDir(): returns a java.io.File object representing the cache of the application on the primary external storage. This cache is not visible to the user and is deleted when the application is uninstalled. There is no mechanism in the Android SDK to delete files in the cache directory, so you need to manage your cache to keep it to a reasonable maximum size. Starting with Android 4.4, the application does not need permission to access its own cache, but with older versions your application needs the READ_EXTERNAL_STORAGE or WRITE_EXTERNAL_STORAGE permission.

Example code to save to a file

To save a file, you need to get the path to the storage you want to use which is used the same way regardless of the type of storage used since all the methods returns a java.io.File object representing the directory to use. Here is an example of using the external storage to save a text file from an Activity :

try {
   // Creates a file in the primary external storage space of the
   // current application.
   // If the file does not exists, it is created.
   File testFile = new File(this.getExternalFilesDir(null), "TestFile.txt");
   if (!testFile.exists())
       testFile.createNewFile();

   // Adds a line to the file
   BufferedWriter writer = new BufferedWriter(new FileWriter(testFile, true /*append*/));
   writer.write("This is a test file.");
   writer.close();
   // Refresh the data so it can seen when the device is plugged in a
   // computer. You may have to unplug and replug the device to see the
   // latest changes. This is not necessary if the user should not modify
   // the files.
   MediaScannerConnection.scanFile(this,
                                   new String[]{testFile.toString()},
                                   null,
                                   null);
} catch (IOException e) {
   Log.e("ReadWriteFile", "Unable to write to the TestFile.txt file.");
}

And here is an example of how to read from the file you just wrote :

String textFromFile = "";
// Gets the file from the primary external storage space of the
// current application.
File testFile = new File(this.getExternalFilesDir(null), "TestFile.txt");
if (testFile != null) {
   StringBuilder stringBuilder = new StringBuilder();
   // Reads the data from the file
   BufferedReader reader = null;
   try {
      reader = new BufferedReader(new FileReader(testFile));
      String line;

      while ((line = reader.readLine()) != null) {
         textFromFile += line.toString();
         textFromFile += "\n";
      }
      reader.close();
   } catch (Exception e) {
      Log.e("ReadWriteFile", "Unable to read the TestFile.txt file.");
   }
}

Introduction : How to save data in your Android application

This is the first post in a series explaining the various ways to save data and application state in an Android application. There are many mechanisms in the Android SDK that can be used to save data, and it is something confusing to decide which one to use and when the operation should be triggered. One of the first and easiest things you’ll want to do when building your application is keeping data entered by the user and explicitly saved. Here are the most commonly used persistent storage modes :

  • The most basic way to save data is to save it to a file on the external storage of the device. This type of storage is accessible by all the other applications and by the user. It can also be seen when the device is mounted as external storage. The files will be kept even if your application is uninstalled unless you get the folder for which to save the files using the getExternalFileDir method. This is the best for files that are edited by the user and should be kept even if your application is removed, for example pictures and documents.
  • You can also save files to an internal storage that is private for your application. In that case, the files are not accessible by the user or by other applications and will be deleted when the application is uninstalled. This is best if you want to save simple data to a file while avoiding the overhead of managing a database.
  • Also, you can save user preferences as key/value pairs with the Preference API to create a SharedPreferences file. You can create an activity to save preferences using the PreferenceActivity or an activity that hosts a PreferenceFragment, both containing Preferences object in their layout instead of view objects.
  • Finally, if you have data with a complex structure to save, each application has its own private SQLLite database to persist data. This data will be destroyed if the application is uninstalled.

Also, there are many cases where the current state of the application needs to be saved because an event was triggered. For example :

  • When the device is rotated, the current activity is recreated so the layout can be adjusted.
  • When your application is sent to the background by the user or by another application, for example if a phone call is received. Also, if another activity in your application is by the user, the current activity is also sent to the background. An activity that is sent to the background can be recycled if the device needs more memory and is recreated when needed again.
  • When the application is explicitly closed by the user.

In those cases, you may need to save the data the user entered in a permanent data store. When the activity is sent to the background or on its way to be destroyed, the onPause event is always raised, followed by the onStop event if the IU is completely hidden, so saving persistent data is best done during the onPause event. Later on, when the user accesses the activity again, the onRestart, onStart and onResume events will be raised if the activity was still in memory and is resumed and the onCreate, onStart, and onResume events will be raised if the activity was recycled and needs to be recreated. So, if you need to display the data again in all cases, you should restore the UI during the onResume.

If it is not necessary to save the data permanently and you only want to save the state of the UI, you can use the onSaveInstanceState event to store the state in a Bundle. This should not be relied upon to save data since the event is not part of the activity lifecycle and is only triggered by the UI  when the activity needs to be recreated or is sent to the background, but not when it is destroyed permanently : it is meant for storing transient view states. Some of the data is already saved by the Android SDK, but you may need to save extra information, for example if you have custom controls. When the user navigates back to the activity and the state of the UI needs to be restored, the bundle containing the state information is accessible from the onRestoreInstanceState event raised if the activity was still in memory, or from the onCreate event raised if the activity was recycled and needs to be recreated.

Here are all the other articles in the series :

Saving data to a file in your Android application
Saving preferences in your Android application
Saving to a SQLite database in your Android application
Storing the state of an activity of your Android application