Chapter 7
Data Persistence

This chapter describes how to persist data in your Android applications. Persisting data is an important topic in application development because users typically expect to reuse data in the future. For Android, there are primarily three basic ways of persisting data:

  • A lightweight mechanism known as shared preferences to save small chunks of data
  • Traditional file systems
  • A relational database management system through the support of SQLite databases

The techniques discussed in this chapter enable applications to create and access their own ­private data. Chapter 8 shows you how to share data across applications.

SAVING AND LOADING USER PREFERENCES

Android provides the SharedPreferences object to help you save simple application data. For example, your application may have an option that enables users to specify the font size used in your application. In this case, your application needs to remember the size set by the user so that the size is set appropriately each time the app is opened. You have several options for saving this type of preference:

  • Save data to a file—You can save the data to a file, but you have to perform some file ­management routines, such as writing the data to the file, indicating how many characters to read from it, and so on. Also, if you have several pieces of information to save, such as text size, font name, preferred background color, and so on, then the task of writing to a file becomes more onerous.
  • Writing text to a database—An alternative to writing to a text file is to use a database. However, saving simple data to a database is overkill, both from a developer's point of view and in terms of the application's run-time performance.
  • Using the SharedPreferences object—The SharedPreferences object, however, saves data through the use of name/value pairs. For example, specify a name for the data you want to save, and then both it and its value will be saved automatically to an XML file.

Accessing Preferences Using an Activity

In the following Try It Out, you see how to use the SharedPreferences object to store application data. You also find out how the stored application data can be modified directly by the user through a special type of activity provided by the Android OS.

Programmatically Retrieving and Modifying the Preferences Values

In the previous section, you saw how the PreferenceActivity class both enables developers to easily create preferences and enables users to modify them during runtime. To make use of these preferences in your application, you use the SharedPreferences class. The following Try It Out shows you how.

PERSISTING DATA TO FILES

The SharedPreferences object enables you to store data that is best stored as name/value pairs—for example, user ID, birth date, gender, driver's license number, and so on. However, sometimes you might prefer to use the traditional file system to store your data. For example, you might want to store the text of poems you want to display in your applications. In Android, you can use the classes in the java.io package to do so.

Saving to Internal Storage

The first way to save files in your Android application is to write to the device's internal storage. The following Try It Out demonstrates how to save a string entered by the user to the device's internal storage.

Saving to External Storage (SD Card)

The previous section showed how you can save your files to the internal storage of your Android device. Sometimes, it would be useful to save them to external storage (such as an SD card) because of its larger capacity, as well as the capability to share the files easily with other users (by removing the SD card and passing it to somebody else). You can use the following steps to save files to external storage:

  1. Using the project created in the previous section as the example (saving text entered by the user to the SD card), modify the onClick() method of the Save button as shown in bold here:
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.OutputStreamWriter;
    import android.app.Activity;
    import android.os.Bundle;
    import android.os.Environment;
    import android.view.View;
    import android.widget.EditText;
    import android.widget.Toast;
        public void onClickSave(View view) {
            String str = textBox.getText().toString();
            try
            {
                //---SD Card Storage---
                File sdCard = Environment.getExternalStorageDirectory();
                File directory = new File (sdCard.getAbsolutePath() +
                    "/MyFiles");
                directory.mkdirs();
                File file = new File(directory, "textfile.txt");
                FileOutputStream fOut = new FileOutputStream(file);
                /*
                FileOutputStream fOut =
                        openFileOutput("textfile.txt",
                                MODE_WORLD_READABLE);
                */
                OutputStreamWriter osw = new
                        OutputStreamWriter(fOut);
                //---write the string to the file---
                osw.write(str);
                osw.flush();
                osw.close();
                //---display file saved message---
                Toast.makeText(getBaseContext(),
                        "File saved successfully!",
                        Toast.LENGTH_SHORT).show();
                //---clears the EditText---
                textBox.setText("");
            }
            catch (IOException ioe)
            {
                ioe.printStackTrace();
            }
        }
  2. The preceding code uses the getExternalStorageDirectory() method to return the full path to the external storage. Typically, it should return the “/sdcard” path for a real device, and “/mnt/sdcard” for an Android emulator. However, you should never try to hardcode the path to the SD card, as manufacturers may choose to assign a different path name to the SD card. Be sure to use the getExternalStorageDirectory() method to return the full path to the SD card.
  3. You then create a directory called MyFiles in the SD card.
  4. Finally, you save the file into this directory.
  5. To load the file from the external storage, modify the onClickLoad() method for the Load button:
        public void onClickLoad(View view) {
            try
            {
                //---SD Storage---
                File sdCard = Environment.getExternalStorageDirectory();
                File directory = new File (sdCard.getAbsolutePath() +
                    "/MyFiles");
                File file = new File(directory, "textfile.txt");
                FileInputStream fIn = new FileInputStream(file);
                InputStreamReader isr = new InputStreamReader(fIn);
                /*
                FileInputStream fIn =
                        openFileInput("textfile.txt");
                InputStreamReader isr = new
                        InputStreamReader(fIn);
                */
                char[] inputBuffer = new char[READ_BLOCK_SIZE];
                String s = "";
                int charRead;
                while ((charRead = isr.read(inputBuffer))>0)
                {
                    //---convert the chars to a String---
                    String readString =
                            String.copyValueOf(inputBuffer, 0,
                                    charRead);
                    s += readString;
                    inputBuffer = new char[READ_BLOCK_SIZE];
                }
                //---set the EditText to the text that has been
                // read---
                textBox.setText(s);
                Toast.makeText(getBaseContext(),
                        "File loaded successfully!",
                        Toast.LENGTH_SHORT).show();
            }
            catch (IOException ioe) {
                ioe.printStackTrace();
            }
        }
  6. Note that in order to write to the external storage, you need to add the WRITE_EXTERNAL_STORAGE permission in your AndroidManifest.xml file:
    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.jfdimarzio.Files"
        android:versionCode="1"
        android:versionName="1.0" >
        <uses-sdk android:minSdkVersion="14"/>
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
        <application
            android:icon="@drawable/ic_launcher"
            android:label="@string/app_name" >
            <activity
                android:label="@string/app_name"
                android:name=".FilesActivity" >
                <intent-filter >
                    <action android:name="android.intent.action.MAIN"/>
                    <category android:name="android.intent.category.LAUNCHER"/>
                </intent-filter>
            </activity>
        </application>
    </manifest>

Choosing the Best Storage Option

The previous sections described three main ways to save data in your Android applications: the SharedPreferences object, internal storage, and external storage. Which one should you use in your applications? Here are some guidelines:

  • If you have data that can be represented using name/value pairs, then use the SharedPreferences object. For example, if you want to store user preference data such as username, background color, date of birth, or last login date, then the SharedPreferences object is the ideal way to store this data. Moreover, you don't really have to do much to store data this way. Simply use the SharedPreferences object to store and retrieve it.
  • If you need to store ad-hoc data then using the internal storage is a good option. For ­example, your application (such as an RSS reader) might need to download images from the web for display. In this scenario, saving the images to internal storage is a good solution. You might also need to persist data created by the user, such as when you have an application that enables users to take notes and save them for later use. In both of these scenarios, using the internal storage is a good choice.
  • There are times when you need to share your application data with other users. For example, you might create an Android application that logs the coordinates of the locations that a user has been to, and subsequently, you want to share all this data with other users. In this scenario, you can store your files on the SD card of the device so that users can easily transfer the data to other devices (and computers) for use later.

CREATING AND USING DATABASES

So far, all the techniques you have seen are useful for saving simple sets of data. For saving relational data, using a database is much more efficient. For example, if you want to store the test results of all the students in a school, it is much more efficient to use a database to represent them because you can use database querying to retrieve the results of specific students. Moreover, using databases enables you to enforce data integrity by specifying the relationships between different sets of data.

Android uses the SQLite database system. The database that you create for an application is only accessible to itself; other applications will not be able to access it.

In this section, you find out how to programmatically create a SQLite database in your Android application. For Android, the SQLite database that you create programmatically in an application is always stored in the /data/data/<package_name>/databases folder.

Creating the DBAdapter Helper Class

A good practice for dealing with databases is to create a helper class to encapsulate all the complexities of accessing the data so that it is transparent to the calling code. For this section, you create a helper class called DBAdapter, which creates, opens, closes, and uses a SQLite database.

In this example, you are going to create a database named MyDB containing one table named contacts. This table has three columns: _id, name, and email.

Using the Database Programmatically

With the DBAdapter helper class created, you are now ready to use the database. In the following sections, you will learn how to perform the regular CRUD (create, read, update and delete) operations commonly associated with databases.

Adding Contacts

The following Try It Out demonstrates how you can add a contact to the table.

Retrieving All the Contacts

To retrieve all the contacts in the contacts table, use the getAllContacts() method of the DBAdapter class, as the following Try It Out shows.

Retrieving a Single Contact

To retrieve a single contact using its ID, call the getContact() method of the DBAdapter class, as the following Try It Out shows.

Updating a Contact

To update a particular contact, call the updateContact() method in the DBAdapter class by passing the ID of the contact you want to update, as the following Try It Out shows.

Deleting a Contact

To delete a contact, use the deleteContact() method in the DBAdapter class by passing the ID of the contact you want to update, as the following Try It Out shows.

Upgrading the Database

Sometimes, after creating and using the database, you might need to add additional tables, change the schema of the database, or add columns to your tables. In this case, you need to migrate your existing data from the old database to a newer one.

To upgrade the database, change the DATABASE_VERSION constant to a value higher than the previous one. For example, if its previous value was 1, change it to 2:

public class DBAdapter {
    static final String KEY_ROWID = "_id";
    static final String KEY_NAME = "name";
    static final String KEY_EMAIL = "email";
    static final String TAG = "DBAdapter";
    static final String DATABASE_NAME = "MyDB";
    static final String DATABASE_TABLE = "contacts";
    static final int DATABASE_VERSION = 2;

When you run the application one more time, you see the following message in the logcat window of Android Studio:

DBAdapter(8705): Upgrading database from version 1 to 2, which
will destroy all old data

For simplicity, simply drop the existing table and create a new one. In real life, you usually back up your existing table and then copy it over to the new table.

SUMMARY

In this chapter, you were introduced to the different ways to save persistent data to your Android device. For simple unstructured data, using the SharedPreferences object is the ideal solution. If you need to store bulk data then consider using the traditional file system. Finally, for structured data, it is more efficient to store it in a relational database management system. For this, Android provides the SQLite database, which you can access easily using the APIs exposed.

Note that for the SharedPreferences object and the SQLite database, the data is accessible only by the application that creates it. In other words, it is not shareable. If you need to share data among different applications, you need to create a content provider. Content providers are discussed in more detail in Chapter 8.

EXERCISES

  1. How do you display the preferences of your application using an activity?
  2. Name the method that enables you to obtain the external storage path for an Android device.
  3. What method is called when a database needs to be upgraded?

    You can find answers to the exercises in the appendix.

WHAT YOU LEARNED IN THIS CHAPTER

Topic Key Concepts
Saving simple user data Use the SharedPreferences object.
Sharing data among activities in the same application Use the getSharedPreferences() method.
Saving to a file Use the FileOutputStream and OutputStreamReader classes.
Reading from a file Use the FileInputStream and InputStreamReader classes.
Saving to external storage Use the getExternalStorageDirectory() method to return the path to the external storage.
Accessing files in the res/raw folder Use the openRawResource() method in the Resources object (obtained via the getResources() method).
Creating a database helper class Extend the SQLiteOpenHelper class.