Persistent Storage – Saving data in Android

Source article:

So you’ve come up with your great app idea. You’ve made the user interface and you’ve made your application model, and now you want to save that model so it can persist between runs. Every app must do this in one form or another, and you’ve got a few options.

Persistent Storage Options

  • Shared Preferences – Private key-value storage of primitive data types (int, double, float, string, etc.)
  • Internal Storage – Private “documents” directory for file storage. Can only be accessed by your application.
  • External Storage – Public file storage (typically on sd card). Available to all apps and users.
  • SQLite Database – Private, on device database.
  • Network Connection – Custom, online implementation of storage, most likely accessed through a web service using some kind of serialization.

Shared Preferences

The shared preferences (accessed through the SharedPreferences class) are essentially a dictionary of key-value pairs capable of storing primitive data types. This kind of storage is ideal for storing things like settings/preferences. To access the shared preferences storage, you must instantiate an instance of SharedPreferences

SharedPreferences prefs = context.getSharedPreferences("prefs", Context.MODE_PRIVATE);

Here we are using the application context (typically the activity you’re in) to get an instance of SharedPreferences (which is a singleton). The first parameter indicates which preferences file we want to access (you can have many), and the second indicates what mode the file is opened in. The Android Developer’s documentation says this about the mode parameter:

Operating mode. Use 0 or MODE_PRIVATE for the default operation, MODE_WORLD_READABLE and MODE_WORLD_WRITEABLE to control permissions. The bit MODE_MULTI_PROCESS can also be used if multiple processes are mutating the same SharedPreferences file. MODE_MULTI_PROCESS is always on in apps targetting Gingerbread (Android 2.3) and below, and off by default in later versions.

Using this class you can read any of the values stored in the file. Changes to the file are made atomically, so they are always current. To read a value, use the “get” functions like getFloat, getString, and getInt. To write values to the shared preferences file, you must get an instance of SharedPreferences.Editor like this:

SharedPreferences.Editor editor = prefs.edit();

The SharedPreferences.Editor class is a public inner class of SharedPreferences and allows the programmer to put/change values into the preferences file using the “put” functions like putInt(), putString(), etc. Keep in mind that your changes are made as an atomic batch, meaning they all happen as a single operation when you call editor.commit(). This maintains the integrity of your data and rolls back any changes that fail to commit properly (say if there were a file error of some kind).

Note that you can only store primitive data types in SharedPreferences. In order to store complex data like objects you must serialize those objects somehow. While in some cases this works well, generally speaking it is not the best use case for shared preferences. As denoted by its name, this storage method is best suited for storing settings and preferences used by the application. This data tends to be small and simple. Adapting this kind of storage to suit a more sophisticated data model can be overly difficult and has obvious limitations.

Internal Storage

If you are familiar with iOS and it’s file storage policies, then Android’s internal storage will seem quite familiar. Every application has a private directory where it can store files that is outside of the application bundle, but remains inaccessible to other applications and the user. These files are tied to the lifetime of the app and are deleted when the app removed from the device, though like the shared preferences they are maintained between updates.

According to Android Developer’s, to create and write a private file to the internal storage:

  1. Call openFileOutput() with the name of the file and the operating mode. This returns a FileOutputStream.
  2. Write to the file with write().
  3. Close the stream with close().

For example:

String FILENAME = "hello_file";
String string = "hello world!";

FileOutputStream fos = openFileOutput(FILENAME, Context.MODE_PRIVATE);

As you can see, there is nothing special about this storage; it is a standard file system. To that end, you are responsible for serializing and deserializing your objects/data within those files.

If you are using internal storage for caching, it is recommended that you use getCacheDir() to get your application’s private cache directory. Like internal storage, this is just a folder where you can write files. The difference is that these files can be deleted by the system at any time if it is running low on space. This doesn’t mean that the system manages them for you (it will NOT delete old files periodically). You are responsible for keeping the contents of the folder current and small in size. The entire point of the folder is that the system can reclaim disk space if it needs to.

Therefore if you are caching data to disk and it is ok for the system to delete that data at any time, this is the best place to store it. (Note that the user can also manually clear this cache through the applications menu without uninstalling your app).

External Storage

External storage refers to any storage on the device that is public to all apps. This could be an external sd card, or a separate partition on the devices internal storage device. Keep in mind that some devices have one, the other, or both. Also note that this storage is often capable of being mounted by another device such as a PC, and might be unavailable or in read-only mode.

You can access this storage by calling getExternalFilesDir() for API level 8+ or getExternalStorageDirectory() when using an earlier version. The newer method allows you to get things like the music or ringtones directory directly (or the root directory if desired), creating a more device-independent experience for the developer.

Since the device is not always mounted, it is important to check its state by calling getExternalStorageState() first. This will tell you whether the storage device is available for read access, write access, both, or neither.

There are 2 main reasons to use external storage over internal storage. The first reason is if you want the data to be public and/or shared with other applications. For example, if you are making an office suite, you may want to allow the user to open your files in other apps (like the email app).

The second reason is space requirements. Most devices partition the two kinds of storage, and internal storage is often times smaller than external storage. If you are storing lots of data it is best to put it on the sd card. If this is the case, Google suggests that you place your files in the /Android/data/<package-name>/files directory. Like other files on external storage, files in this directory will be public, but will be deleted when you application is deleted. This way your files are public, but won’t be left on the device after you app has been deleted (which is a pet peeve of mine so do it!).

Notes on external storage

There are some important things to know when storing media on external storage. Many apps scan external storage for things like music, ringtones, videos, pictures, etc. It may be the case that you want to put an mp3 on the sd card, but don’t want it to show up in the user’s music app. If you put an empty file in your directory called .nomedia the folder will be ignored by the scanner. Likewise, if your app scans external storage for files, it should respect this convention.

If you do want your files to be shared, consider putting them into the default directories Music/, Podcasts/, Ringtones/, Alarms/, Notifications/, etc. These are the expected locations for these files, and if the user goes looking for them in the file system, this is where they will expect to find them.

You can also put cache files on external storage in much the same way you do with internal storage (read the article).

SQLite Databases

SQLite is a C based relational database implementation that is widely used in embedded systems because it is simple, small, and can be used on many different platforms. As opposed to more powerful database implementations, like MS SQL Server, SQLite does not run as its own process. Rather, it runs in the same process as the consumer and is stored as a single file on the file system. To this end, it very small and portable but is not as efficient.

Android has full support for SQLite databases, including classes and library support. To use SQLite you will extend the SQLiteHelper class. This object will serve as act as a communication layer between the database and your application. It will handle things like database creation when you app runs for the first time, querying, and sending/receiving data.

When you query a SQLite database using the Android libraries, you will get back a Cursor, which is essentially a collection of row data for your query. Using this you can extract your data into the data-structure of your choosing.

SQLite implements most of the sql standard, but if you are familiar with higher-powered databases you will quickly see that SQLite severely handicapped compared to its brethren. However, it can be an extremely useful data storage mechanism. Why? Because using a relational database you can create a rich and robust data model from which you can perform queries, filters, and analytics.

Databases when understood provide a flexible and powerful storage platform, but when misunderstood can be a terrible stumbling block. Make sure that before you step into databases of any kind that you learn how to design them and how to use them.

Network Connection

The last storage method is ambiguous to say the least. With an Internet connection, your options are virtually unlimited. You can connect directly to an online database, or to a custom web service, or to other devices. What ever you choose to do, keep in mind that the device must be connected to the Internet in order to store data.

If the user is using your app and their Internet connection goes out, you don’t want to lose their data. Losing data is a quick way to get uninstalled. Generally speaking, if you are saving your data in the cloud it is best to use some combination of local and remote storage so you can backup that data locally when Internet connection is lost.