to File
E
F
Write with FileOutputStream143
Using the filesystem
"ERROR /sdcard path not available (did you create " + " an SD image with the mksdcard tool," + " and start emulator with -sdcard " + <path_to_file> option?");
}
File rFile =
new File("/sdcard/unlocking_android/" + fileName); if (rFile.exists() && rFile.canRead()) {
FileInputStream fis = null; try {
fis = new FileInputStream(rFile);
byte[] reader = new byte[fis.available()]; while (fis.read(reader) != -1) {
}
readOutput.setText(new String(reader)); } catch (IOException e) {
// log and or handle } finally { if (fis != null) { try { fis.close(); } catch (IOException e) { // swallow } } } } else { readOutput.setText(
"Unable to read/write sdcard file, see logcat output"); }
} }
We first define a name for the file to create
B
. In this example, we append a time- stamp to create a unique name each time this example application runs. After we have the filename, we create a File object reference to the removable storage directoryC
. From there, we create a File reference to a new subdirectory, /sdcard/ unlocking_androidD
. The File object can represent both files and directories. After we have the subdirectory reference, we call mkdir() to create it if it doesn’t already exist.With our directory structure in place, we follow a similar pattern to create the actual file. We instantiate a reference File object
E
, and then call createFile() to create a file on the filesystem. When we have the File and know it exists and that we’re allowed to write to it, we use a FileOutputStream to write data into the fileF
.After we create the file and have data in it, we create another File object with the full path to read the data back
G
. With the File reference, we then create a File- InputStream and read back the data that was earlier stored in the fileH
.As you can see, working with files on the SD card resembles standard java.io.File code. A fair amount of boilerplate Java code is required to make a robust solution, with permissions and error checking every step of the way, and logging about what’s
G
Read with FileInputStream
happening, but it’s still familiar and powerful. If you need to do a lot of File han- dling, you’ll probably want to create some simple local utilities for wrapping the mun- dane tasks so you don’t have to repeat them over and over again. You might want to use or port something like the Apache commons.io package, which includes a File- Utils class that handles these types of tasks and more.
The SD card example completes our exploration of the various ways to store differ- ent types of file data on the Android platform. If you have static predefined data, you can use res/raw; if you have XML files, you can use res/xml. You can also work directly with the filesystem by creating, modifying, and retrieving data in files, either in the local internal filesystem or on the SD card, if one is available.
A more complex way to deal with data—one that supports more robust and spe- cialized ways to persist information—is to use a database, which we’ll cover in the next section.
5.3
Persisting data to a database
Android conveniently includes a built-in relational database.1SQLite doesn’t have all the features of larger client/server database products, but it includes every- thing you need for local data storage. At the same time, it’s quick and relatively easy to work with.
In this section, we’ll cover working with the built-in SQLite database system, from creating and querying a database to upgrading and working with the sqlite3 tool available in the adb shell. We’ll demonstrate these fea- tures by expanding the WeatherReporter application from chapter 4. This application uses a database to store the user’s saved locations and persists user prefer- ences for each location. The screenshot shown in figure 5.4 displays the saved data that the user can select from; when the user selects a location, the app retrieves infor- mation from the database and shows the corresponding weather report.
We’ll start by creating WeatherReporter’s database.
5.3.1 Building and accessing a database
To use SQLite, you have to know a bit about SQL in general. If you need to brush up on the background of the basic commands, such as CREATE, INSERT, UPDATE, DELETE, and SELECT, then you might want to take a look at the SQLite documentation at
http://www.sqlite.org/lang.html.
1 Check out Charlie Collins’ site for Android SQLLite basics: http://www.screaming-penguin.com/node/
7742.
Figure 5.4
The WeatherReporter Saved Locations screen, which pulls data from a SQLite database
145
Persisting data to a database
For now, we’ll jump right in and build a database helper class for our application. You need to create a helper class so that the details concerning creating and upgrad- ing the database, opening and closing connections, and running through specific queries are all encapsulated in one place and not otherwise exposed or repeated in your application code. Your Activity and Service classes can use simple get and insert methods, with specific bean objects representing your model, rather than database-specific abstractions such as the Android Cursor object. You can think of this class as a miniature Data Access Layer (DAL).
The following listing shows the first part of our DBHelper class, which includes a few useful inner classes.
public class DBHelper {
public static final String DEVICE_ALERT_ENABLED_ZIP = "DAEZ99"; public static final String DB_NAME = "w_alert";
public static final String DB_TABLE = "w_alert_loc"; public static final int DB_VERSION = 3;
private static final String CLASSNAME = DBHelper.class.getSimpleName(); private static final String[] COLS = new String[]
{ "_id", "zip", "city", "region", "lastalert", "alertenabled" }; private SQLiteDatabase db;
private final DBOpenHelper dbOpenHelper; public static class Location {
public long id; public long lastalert; public int alertenabled; public String zip; public String city; public String region;
. . . Location constructors and toString omitted for brevity }
private static class DBOpenHelper extends SQLiteOpenHelper {
private static final String DB_CREATE = "CREATE TABLE " + DBHelper.DB_TABLE
+ " (_id INTEGER PRIMARY KEY, zip TEXT UNIQUE NOT NULL," + "city TEXT, region TEXT, lastalert INTEGER, "
+ "alertenabled INTEGER);";
public DBOpenHelper(Context context, String dbName, int version) { super(context, DBHelper.DB_NAME, null, DBHelper.DB_VERSION); }
@Override
public void onCreate(SQLiteDatabase db) { try {
db.execSQL(DBOpenHelper.DB_CREATE); } catch (SQLException e) {
Log.e("ProviderWidgets", DBHelper.CLASSNAME, e); }
}
Listing 5.10 Portion of the DBHelper class showing the DBOpenHelper inner class