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


25 Comments

Chris

That is essentially correct.  Provided an application requests (and is granted by allowing it to be installed) access to external storage, it can do whatever it wants with the SD card.  

Simon Belmont

I have a question. When I use MODE_WORLD_READABLE, and MODE_WORLD_WRITEABLE, does that just mean that the file that is created has the User permissions set to -rw-?

In other words, does any file with read and write access turned on, stored in internal memory, become readable and writable by any other app, despite the UID sandboxing? I mean, I've looked at a lot of files in my handset's internal memory, and most seem to have read and write turned on by default, which seems like a security risk.

Chris

Hello Simon, sorry for the slow reply.  I believe that is accurate.  Another application / user might have to "find" the file but once they did, they could read / write it (depending on WORLD_READABLE / WORLD_WRITEABLE).

Rupert

Thanks for all this work. I am writing an app for our volunteer fire station and this series has been very useful. One quick editorial comment. the following text: "you need to check it’s state..." should read "you need to check its state..."

Chris

If you're writing the file to the default app storage, it will be hidden away in the /data/ directory that you can't access unless your device is rooted. If you write it to external storage than you can choose the directory or there will be a similar, unprotected data directory.

Esteleth

Hi, I'm developing a simple android sketching app as a part of a project. I want to know if it is possible to be able to save the app data in my computer. For more detail:

I am interested in the coordinates of the points where I touch the screen on my android device and want to use these for further work in my computer. Therefore, I need a way that I can access these from my computer. I have seen that I can print out the coordinates in Android Studio at runtime, with the device connected through USB. Is there any way that I can save these in my computer in the text file? I created a list and pushed all the coordinates into that, but am not sure where that is being saved and how to write that in a file in my own computer.

This is the relevant code. The commented part is what I tried doing but didn't work:

[...]
import java.io.*;
import java.io.BufferedWriter;
import java.util.ArrayList;

public class DrawingView extends View {
private static final String TAG = "MyActivity";
// Writer writer;
// FileOutputStream file;
ArrayList coords;

//drawing path
private Path drawPath;
//drawing and canvas paint
private Paint drawPaint, canvasPaint;
//initial color
private int paintColor = 0xFF660000;
//canvas
private Canvas drawCanvas;
//canvas bitmap
private Bitmap canvasBitmap;
private float brushSize, lastBrushSize;
private boolean erase=false;

public DrawingView(Context context, AttributeSet attrs){
super(context, attrs);
setupDrawing();
coords = new ArrayList();
}

private void setupDrawing(){
//get drawing area setup for interaction
drawPath = new Path();
drawPaint = new Paint();
drawPaint.setColor(paintColor);
drawPaint.setAntiAlias(true);
drawPaint.setStrokeWidth(20);
drawPaint.setStyle(Paint.Style.STROKE);
drawPaint.setStrokeJoin(Paint.Join.ROUND);
drawPaint.setStrokeCap(Paint.Cap.SQUARE);
canvasPaint = new Paint(Paint.DITHER_FLAG);
brushSize = getResources().getInteger(R.integer.medium_size);
lastBrushSize = brushSize;
drawPaint.setStrokeWidth(brushSize);
}

[...]

@Override
public boolean onTouchEvent(MotionEvent event) {
//detect user touch
float touchX = event.getX();
float touchY = event.getY();

String x = Float.toString(touchX);
String y = Float.toString(touchY);
String c = "(" + x + ", " + y + ")";
Log.d(TAG, c);
coords.add(c);

switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
drawPath.moveTo(touchX, touchY);
break;
case MotionEvent.ACTION_MOVE:
drawPath.lineTo(touchX, touchY);
break;
case MotionEvent.ACTION_UP:
drawCanvas.drawPath(drawPath, drawPaint);
drawPath.reset();
break;
default:
return false;
}
//try {
//writer = new BufferedWriter(new OutputStreamWriter(file, "utf-8"));
//writer.write("Something");
//} catch(IOException e){
////report
//}

invalidate();
return true;
}

[...]

}

Leave a Comment