Chapter 23

Accessing Files

While Android offers structured storage, via preferences and databases, sometimes a simple file will suffice. Android offers two models for accessing files: one for files prepackaged with your application and one for files created on-device by your application. Both of these models are covered in this chapter.

You and the Horse You Rode in On

Let’s suppose you have some static data you want to ship with the application, such as a list of words for a spell checker. The easiest way to deploy that is to place the file in the res/raw directory, so it will be put in the Android application APK file as part of the packaging process as a raw resource.

To access this file, you need to get yourself a Resources object. From an activity, that is as simple as calling getResources(). A Resources object offers openRawResource() to get an InputStream on the file you specify. Rather than a path, openRawResource() expects an integer identifier for the file as packaged. This works just like accessing widgets via findViewById(). For example, if you put a file named words.xml in res/raw, the identifier is accessible in Java as R.raw.words.

Since you can get only an InputStream, you have no means of modifying this file. Hence, it is really useful just for static reference data. Moreover, since it is unchanging until the user installs an updated version of your application package, either the reference data must be valid for the foreseeable future or you will need to provide some means of updating the data. The simplest way to handle that is to use the reference data to bootstrap some other modifiable form of storage (e.g., a database), but you end up with two copies of the data in storage.

An alternative is to keep the reference data as is but keep modifications in a file or database, and merge them together when you need a complete picture of the information. For example, if your application ships a file of URLs, you could have a second file that tracks URLs added by the user or reference URLs that were deleted by the user.

In the Files/Static sample project, you will find a reworking of the list box example from Chapter 7, this time using a static XML file instead of a hardwired array in Java. The layout is the same:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent" >
  <TextView
    android:id="@+id/selection"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
  />
  <ListView
    android:id="@android:id/list"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:drawSelectorOnTop="false"
  />
</LinearLayout>

In addition to that XML file, you also need an XML file with the words to show in the list:

<words>
  <word value="lorem" />
  <word value="ipsum" />
  <word value="dolor" />
  <word value="sit" />
  <word value="amet" />
  <word value="consectetuer" />
  <word value="adipiscing" />
  <word value="elit" />
  <word value="morbi" />
  <word value="vel" />
  <word value="ligula" />
  <word value="vitae" />
  <word value="arcu" />
  <word value="aliquet" />
  <word value="mollis" />
  <word value="etiam" />
  <word value="vel" />
  <word value="erat" />
  <word value="placerat" />
  <word value="ante" />
  <word value="porttitor" />
  <word value="sodales" />
  <word value="pellentesque" />
  <word value="augue" />
  <word value="purus" />
</words>

While this XML structure is not exactly a model of space efficiency, it will suffice for a demo.

The Java code now must read in that XML file, parse out the words, and put them someplace for the list to pick up:

public class StaticFileDemo extends ListActivity {
  TextView selection;
  ArrayList<String> items=new ArrayList<String>();
  
  @Override
  public void
onCreate(Bundle icicle) {
    super.
onCreate(icicle);
    
setContentView(R.layout.main);
    selection=(TextView)
findViewById(R.id.selection);
    
    try {
      InputStream in=
getResources().openRawResource(R.raw.words);
      DocumentBuilder builder=DocumentBuilderFactory
                               .
newInstance()
                               .
newDocumentBuilder();
      Document doc=builder.
parse(in, null);
      NodeList words=doc.
getElementsByTagName("word");
      
      for (int i=0;i<words.
getLength();i++) {
        items.
add(((Element)words.item(i)).getAttribute("value"));
      }
      
      in.
close();
    }
    catch (Throwable t) {
      Toast
        .
makeText(this, "Exception: "+t.toString(), 2000)
        .
show();
    }
    
    
setListAdapter(new ArrayAdapter<String>(this,
                                 android.R.layout.simple_list_item_1,
                                 items));
  }

  public void
onListItemClick(ListView parent, View v, int position,
                  long id) {
    selection.
setText(items.get(position).toString());
  }
}

The differences mostly lie within onCreate(). We get an InputStream for the XML file (getResources().openRawResource(R.raw.words)), then use the built-in XML parsing logic to parse the file into a DOM Document, pick out the word elements, and then pour the value attributes into an ArrayList for use by the ArrayAdapter.

The resulting activity looks the same as before, as shown in Figure 23–1, since the list of words is the same, just relocated.

image

Figure 23–1. The StaticFileDemo sample application

Of course, there are even easier ways to have XML files available to you as prepackaged files, such as by using an XML resource, as discussed in Chapter 20. However, while this example used XML, the file could just as easily have been a simple one-word-per-line list or in some other format not handled natively by the Android resource system.

Readin’ ’n Writin’

Reading or writing your own, application-specific data files is nearly identical to what you might do in a desktop Java application. The key is to use openFileInput() or openFileOutput() on your Activity or other Context to get an InputStream or OutputStream, respectively. From that point forward, it is not much different from regular Java I/O logic:

  • Wrap those streams as needed, such as using an InputStreamReader or OutputStreamWriter for text-based I/O.
  • Read or write the data.
  • Use close() to release the stream when done.

If two applications both try reading a notes.txt file via openFileInput(), each will access its own edition of the file. If you need to have one file accessible from many places, you probably want to create a content provider, as described in Chapter 27.

Note that openFileInput() and openFileOutput() do not accept file paths (e.g., path/to/file.txt), just simple filenames.

Here is the layout for the world’s most trivial text editor, pulled from the Files/ReadWrite sample application:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent"
  android:orientation="vertical">
  <Button android:id="@+id/close"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Close" />
  <EditText
    android:id="@+id/editor"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:singleLine="false"
    android:gravity="top"
    />
</LinearLayout>

All we have here is a large text-editing widget, with a Close button above it.

The Java is only slightly more complicated:

package com.commonsware.android.readwrite;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;

public class ReadWriteFileDemo extends Activity {
  private final static String NOTES="notes.txt";
  private EditText editor;
  
  @Override
  public void
onCreate(Bundle icicle) {
    super.
onCreate(icicle);
    
setContentView(R.layout.main);
    editor=(EditText)
findViewById(R.id.editor);
    
    Button btn=(Button)
findViewById(R.id.close);
    
    btn.
setOnClickListener(new Button.OnClickListener() {
      public void
onClick(View v) {
        
finish();
      }
    });
  }
  
  public void
onResume() {
    super.
onResume();
    
    try {
      InputStream in=
openFileInput(NOTES);
      
      if (in!=null) {
        InputStreamReader tmp=new
InputStreamReader(in);
        BufferedReader reader=new
BufferedReader(tmp);
        String str;
        StringBuffer buf=new
StringBuffer();
        
        while ((str = reader.
readLine()) != null) {
          buf.
append(str+" ");
        }
        
        in.
close();
        editor.
setText(buf.toString());
      }
    }
    catch (java.io.FileNotFoundException e) {
      // that's OK, we probably haven't created it yet
    }
    catch (Throwable t) {
      Toast
        .
makeText(this, "Exception: "+t.toString(), 2000)
        .
show();
    }
  }
  
  public void
onPause() {
    super.
onPause();
    
    try {
      OutputStreamWriter out=
          new
OutputStreamWriter(openFileOutput(NOTES, 0));
      
      out.
write(editor.getText().toString());
      out.
close();    
    }
    catch (Throwable t) {
      Toast
        .
makeText(this, "Exception: "+t.toString(), 2000)
        .
show();
    }
  }
}

First, we wire up the button to close our activity when clicked by using setOnClickListener() to invoke finish() on the activity.

Next, we hook into onResume(), so we get control when our editor is coming back to life, from a fresh launch or after having been frozen. We use openFileInput() to read in notes.txt and pour the contents into the text editor. If the file is not found, we assume this is the first time the activity was run (or the file was deleted by other means), and we just leave the editor empty.

Finally, we hook into onPause(), so we get control as our activity is hidden by another activity or closed, such as via our Close button. Here, we use openFileOutput() to open notes.txt, into which we pour the contents of the text editor.

The net result is that we have a persistent notepad, as shown in Figures 23–2 and 23–3. Whatever is typed in will remain until deleted, surviving our activity being closed, the phone being turned off, or similar situations.

image

Figure 23–2. The ReadWriteFileDemo sample application, as initially launched

image

Figure 23–3. The same application, after entering some text

You are also welcome to read and write files on external storage (a.k.a., the SD card). Use Environment.getExternalStorageDirectory() to obtain a File object at the root of the SD card. Starting with Android 1.6, you will also need to hold permissions to work with external storage (e.g., WRITE_EXTERNAL_STORAGE). Permissions are covered in Chapter 28.

Bear in mind that external storage is accessible by all applications, whereas openFileInput() and openFileOutput() are in an application-private area.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.112.82