UPDATE: It looks like the code for today was accidentally labeled DayTwentyTwo instead DayTwentyThree. I'll go back and fix this as soon I have a chance.

It’s time to continue our sub-series on persistence as part of the 31 Days of Android.  Yesterday we went over Shared Preferences which is a method for persisting key value pairs.  These can be only readable by your application, or you can open them up to reading or writing by other apps.  Today we’re going to cover another method of persisting data, one that anyone that has programmed in the past is probably familiar with:  file storage.  The Java language supports many different methods for file access, and these can be used in Android as well.  There are two methods of file storage we’ll look at:  internal storage and external storage.  You can access our starter code here.

 

Using Internal Storage

Internal storage refers to the on device hard drive and does not include SD cards.  Internal storage gives you the ability to prevent other applications from accessing the files you save and are tied directly to your app.  Lets look at how to write to a local file.  Open src/com.daytwentytwo/DayTwentyTwoActivity.java and find the onClickListener for button1.  Here you can add the following code:

lblTextViewOne.setText(editText1.getText());                            
try {
    FileOutputStream fos = openFileOutput("DayTwentyTwoFile", Context.MODE_PRIVATE);
    fos.write(editText1.getText().toString().getBytes());
    fos.close();
} catch (Exception e) {
    e.printStackTrace();
}

Here you’re opening a file named “DayTwentyTwoFile” in the mode MODE_PRIVATEMODE_PRIVATE will create a new file or overwrite one if it already exists with the same name.  MODE_APPEND will create the file if it doesn’t exist and allow you to append to the file if it does exist.  MODE_WORLD_READABLE means the file is readable by any other application and MODE_WORLD_WRITEABLE means the file is writeable by any other application.  You can open a file for appending and make it world readable / writeable by ORing the two values like so:

FileOutputStream fos = openFileOutput("DayTwentyTwoFile", 
    Context.MODE_APPEND | Context.MODE_WORLD_READABLE);

When you run your app, you can click on the button which should write to the file, but you don’t have a way in the app to make sure it’s working yet.  To do this, you need to use the DDMS perspective in Eclipse.  After you’ve opened DDMS, select the emulator in the Devices view in the top left.  Then in the main content window, at the top, select File Explorer.  By default you’ll find all apps under /data/data/packagename.  If you navigate to /data/data/com.daytwentytwo/files you should see a DayTwentyTwoFile.  Using the buttons at the top right of that view, you can choose to move the file off of the phone to your computer, and back from your computer to the phone.

DDMS

Once you’ve copied that file to your local computer and open it, you’ll see that whatever you had in the EditText when you tapped the button made it into the file.

 

Reading from the Local File

Now that you’ve handled writing to the local file, it’s a small step to reading from that file.  Locate the button2 onClickListener and add the following code:

try {
    BufferedReader inputReader = new BufferedReader(new InputStreamReader(
            openFileInput("DayTwentyTwoFile")));
    String inputString;
    StringBuffer stringBuffer = new StringBuffer();                
    while ((inputString = inputReader.readLine()) != null) {
        stringBuffer.append(inputString + "\n");
    }
    lblTextViewOne.setText(stringBuffer.toString());
} catch (IOException e) {
    e.printStackTrace();
}

It’s slightly more complex than the write was but here you’re opening up the file and reading it into a StringBuffer.  Now when you run your app and tap the second button, you should see the text that was previously stored in the file loaded into the TextView at the top of the screen.

 

Using External Storage

External storage is typically either a removable storage media (i.e. SD Card) or an internal non-removable storage that is accessed in the same manner.  In either case, anything written to this location can be accessed by other programs, moved to and from the computer, or deleted without any notice to your application.  One factor when considering using external storage vs. internal storage is that older devices have limited onboard storage and using external storage may be necessary.  On newer devices, this is less of a concern.  The first thing you’ll need to do before you can write to external storage is add the permission required to do so to your manifest file:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> 

Users will see that you app requires this permission when they go to install it.

Next, prior to trying to access the external storage, you need to check its state using the Environment.getExternalStorageState method.  This can return a few different options.  If it equals Environment.MEDIA_MOUNTED then you should be safe to read and write.  If you get Environment.MEDIA_MOUNTED_READ_ONLY then you can only safely read.  If you get anything else, it isn’t safe to access the external storage.  Once you know you have access, there are two locations it is acceptable to write files to in external storage:  your app’s data directory and special public directories.  Let’s look at your app’s data directory first. 

In API level 8 and greater (Android 2.2 and greater) calling getExternalFilesDir will open a file at the root of your apps external storage directory.  This method will take in a type that you can use to specify a sub-directory like Environment.DIRECTORY_MUSIC if you want to put files in.  Alternatively if you pass in null you’ll get the root of your app’s data directory.  To write a file to the root of your app’s directory in 2.2 or greater, you’d do the following:

try {
    FileOutputStream fos = openFileOutput("DayTwentyTwoFile",
            Context.MODE_APPEND | Context.MODE_WORLD_READABLE);
    fos.write(editText1.getText().toString().getBytes());
    fos.close();
 
    String storageState = Environment.getExternalStorageState();
    if (storageState.equals(Environment.MEDIA_MOUNTED)) {
        File file = new File(getExternalFilesDir(null),
                "DayTwentyTwoFileTwo");
        FileOutputStream fos2 = new FileOutputStream(file);
        fos2.write(editText1.getText().toString().getBytes());
        fos2.close();
    }
} catch (Exception e) {
    e.printStackTrace();
}

Here you’re doing the original write to internal storage and then checking to see if the external storage is mounted.  If it is, you’re creating a new file at the root of your app’s directory and writing out the same data.  Reading the file is the same as before, except you’d pass in a new FileInputStream where before you were calling openFileInput

String storageState = Environment.getExternalStorageState();
if (storageState.equals(Environment.MEDIA_MOUNTED)) {
    File file = new File(getExternalFilesDir(null),
            "DayTwentyTwoFileTwo");
    
 
    BufferedReader inputReader2 = new BufferedReader(
            new InputStreamReader(new FileInputStream(file)));
    String inputString2;
    StringBuffer stringBuffer2 = new StringBuffer();
    while ((inputString2 = inputReader2.readLine()) != null) {
        stringBuffer2.append(inputString2 + "\n");
    }
    lblTextViewOne.setText(stringBuffer2.toString());
}

If you’re developing for pre 2.2 you won’t have access to the getExternalFilesDir method.  Instead you must call getExternalStorageDirectory which will return the path to the root of your external storage.  You are then responsible for writing files to your app’s data directory at the following path:  /Android/data/<package>/files.  So for the sample code, you’d write files to /Android/data/com/daytwentytwo/files.  If and when your application is uninstalled, any files in your app’s data directory will be removed in 2.2 or greater.

Writing Shared Files to External Storage

Much like when you call getExternalFilesDir and you can pass in a type, there are public folders on external storage that have specific types.  When you write a file to one of these directories, they are shared among different apps.  For example, if you waned to write music tracks to a shared music folder, you’d get the directory and pass in Environment.DIRECTORY_MUSIC:

File file = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC);

Similarly to the pre 2.2 issue we faced before, if you are using pre 2.2 and want to write to a public folder, you need to call getExternalStorageDirectory and write to one of the shared directories (so “Music/” in the previous case). 

 

You should now be capable of writing and reading files on your Android device in your applications.  Remember that users can see how much space your app is taking up in user data.  Furthermore they can clear it freely from the Manage Applications area of settings.  Knowing that, and that anything can access and change files on external storage, you should remember that these files could be removed outside of your application. 

You can download the finished sample code from today here.


Chris Risner