One of the best things about the Android platform is that it tends to be less restrictive than other smartphone operating systems. Sure, Android has an easy to use preference system that allows you to keep track of key value pairs, but if you need a more flexible approach, Android also allows you to read and write directly to a removable SD card. Take that iOS!
The example that follows shows both how to access the SD card on an Android device, and perform simple read and write operations. You can follow along with the step-by-step tutorial, or download and import the entire project into Eclipse. While the code may look a little intimidating to beginners, those familiar with Java will notice that very little of the code is Android specific. In other words, manipulating files on Android is very much like manipulating files on any platform where Java runs.
1. Start a new Android project in Eclipse. Target Android 1.6 or higher. Be sure to rename the startup file Main.java.
2. Add permissions to the manifest. Open /res/AndroidManifest.xml and add both read and write permissions to the project.
AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.authorwjf.direct_file_io"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="4"
android:targetSdkVersion="15" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".Main"
android:label="@string/title_activity_main" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
3. Moving on to the /res/layout folder, you will want to define your main.xml layout file. The layout for this project is a straightforward linear layout that contains two edit text fields and three buttons.
main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:background="#000000" >
<EditText
android:layout_margin="10dip"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:hint="enter some text"
android:id="@+id/first_input_line"/>
<EditText
android:layout_margin="10dip"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:hint="enter some more text"
android:id="@+id/second_input_line"/>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal" >
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/clear_button"
android:text="Clear"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/load_button"
android:text="Load"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/save_button"
android:text="Save"/>
</LinearLayout>
</LinearLayout>
4. Now its time to write the Main.java code. Start with some includes, overriding the on start to wire up the three buttons, and a call back to handle the button presses.
Main.java
package com.authorwjf.direct_file_io;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import android.os.Bundle;
import android.os.Environment;
import android.app.Activity;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.EditText;
import android.widget.Toast;
public class Main extends Activity implements OnClickListener{
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
findViewById(R.id.clear_button).setOnClickListener(this);
findViewById(R.id.save_button).setOnClickListener(this);
findViewById(R.id.load_button).setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch(v.getId()) {
case R.id.clear_button:
((EditText) findViewById(R.id.first_input_line)).setText("");
((EditText) findViewById(R.id.second_input_line)).setText("");
break;
case R.id.load_button:
load();
break;
case R.id.save_button:
save();
break;
}
}
}
5. We will implement three private functions to handle the file I/O. Except for the calls to get the external storage system directory, these are standard Java techniques for creating a file if it doesn’t exist (ensure), and then reading and writing.
Main.java
private void ensure() {
File sdcard = Environment.getExternalStorageDirectory();
File file = new File(sdcard,"/my_file.txt");
if(!file.exists()) {
try {
file.createNewFile();
} catch (IOException e) {
Toast.makeText(getApplicationContext(),"Error creating file!",Toast.LENGTH_LONG).show();
}
}
}
private void load() {
ensure();
File sdcard = Environment.getExternalStorageDirectory();
File file = new File(sdcard,"/my_file.txt");
try {
BufferedReader br = new BufferedReader(new FileReader(file));
String line;
if ((line = br.readLine()) != null) {
String[] content=line.toString().split(":");
((EditText) findViewById(R.id.first_input_line)).setText(content[0]);
((EditText) findViewById(R.id.second_input_line)).setText(content[1]);
}
br.close();
}
catch (IOException e) {
Toast.makeText(getApplicationContext(),"Error reading file!",Toast.LENGTH_LONG).show();
}
}
private void save() {
ensure();
String firstLine = ((EditText) findViewById(R.id.first_input_line)).getText().toString().trim();
String secondLine = ((EditText) findViewById(R.id.second_input_line)).getText().toString().trim();
File sdcard = Environment.getExternalStorageDirectory();
File file = new File(sdcard,"/my_file.txt");
try {
FileWriter fw = new FileWriter(file);
BufferedWriter bw = new BufferedWriter(fw);
bw.write(firstLine+":"+secondLine);
bw.close();
} catch (IOException e) {
Toast.makeText(getApplicationContext(),"Error writing file!",Toast.LENGTH_LONG).show();
}
}
We are almost ready to run the project. If you are loading it directly to your device, you don’t need to do anything else. If you plan to test it on the emulator, you will want to go in and edit your AVD profile to include SD storage (Figure A).
Figure A
Once you have the app on your phone or emulator, the rest should be pretty self-explanatory. You can type some input into either of the two edit text fields and then press Save. Pressing Clear, (you guessed it) clears the fields. You can use the Load button to repopulate the form with previously saved values. Of course saving two lines of text directly to the SD card is overkill, but the file I/O concepts will scale easily to meet any number of real-world scenarios. (Figure B)
Figure B