In this exercise, you will construct a simple notes list that lets the user add new notes but not edit them. The exercise demonstrates:
ListActivities and creating and handling menu
options. Open up the Notepadv1 project in Eclipse.
Notepadv1 is a project that is provided as a starting point. It
takes care of some of the boilerplate work that you have already seen if you
followed the Hello
Android tutorial.
Notepadv1 and hit Choose.Notepadv1 in the Project name: field and also see the Location:
filled out with the path you selectedAndroidManifest.xml, or some
problems related to an Android zip file, right click on the project and
select Android Tools->Fix Project Properties from the popup
menu (the project is looking in the wrong location for the library file,
this will fix it for you). For this exercise, we are just going to use a SQLite database directly to store our data. This is useful if only your application will ever access or modify the data. If you wish for the data to be accessible from other activities, you have to expose the data using a ContentProvider.
If you are interested, you can find out more about
content providers or the whole
subject of Storing, Retrieving, and Exposing Data.
The NotePad sample in the samples/ folder of the SDK also has an example of how
to create a ContentProvider.
Take a look at the NotesDbAdapter class — this class is provided to
encapsulate data access to a SQLite database that will hold our notes data
and allow us to update it.
At the top of the class are some constant definitions that can be used in our application to look up data from the right field names in the database. There is also a database creation string defined which is used to create a new database schema if one doesn't exist already.
Our database will have the name data, and have a single table called
notes, which in turn has three fields: _id, title and
body. The _id is named in line with a convention used in a number of
different places inside the Android SDK to help keep a track of state. The _id
usually has to be specified when querying or updating the database (in the column projections
and so on). The other two fields are simple text fields that we have created to store data in.
The constructor for NotesDbAdapter takes a context to allow it to communicate with aspects
of the Android operating system. This is quite common for classes that need to touch the
Android system in some way. Activity implements the Context class so usually you will just pass
this in from Activities that call classes needing a Context.
The open() method checks to see if the database is already present, and if not
it creates a new one and executes the schema creation SQL command to set it up.
close() just closes the database, releasing resources related to the
connection.
createNote() takes strings for the title and body of a new note to create,
and then creates that note in the database. Assuming the new note is created successfully, the
method also returns the rowID for the newly created note.
deleteNote() takes a rowId for a particular note, and deletes that note from
the database.
fetchAllNotes() issues a query to return a cursor over all notes in the
database. The query call is worth examination and understanding. The first field is the
name of the database table to query (in this case "notes").
The next is the list of columns we want returned, in this case we want the _id,
title and body columns so these are specified in a String array
created in-line. The remaining fields are, in order: selection,
selectionArgs, groupBy, having and orderBy.
Having these all null means we want all data, need no grouping, and will take the default
order. See SQLiteDatabase for more details.
Note: A cursor is returned rather than a collection of rows. This allows Android to use resources efficiently -- instead of putting lots of data straight into memory the cursor will retrieve and release data as it is needed, which is much more efficient for tables with lots of rows.
fetchNote() is similar to fetchAllNotes() but just gets one note
with the rowID we specify. The first parameter (set to true) indicates that we are interested
in one distinct result, and the selection parameter has been specified to search
only for the row "where _id =" the rowID we passed in
And finally, updateNote() takes a rowID, title and body, and uses a
ContentValues instance to update the note with the given
rowID.
Most Activities will have a layout associated with them. The layout will be the "face" of the activity to the user. In this case our layout will take over the whole screen and provide a list of notes.
Full screen layouts are not the only option for an Activity however. You might also want to use a floating layout (for example, a dialog or alert), or perhaps you don't need a layout at all (the activity will be invisible to the user unless you specify some kind of layout for it to use).
Open the notepad_list.xml file in res/layout
and
take a look at it:
This is a layout definition file with a default starting point in it, we have provided this as a convenience to get you going quickly.
<?xml version="1.0" encoding="utf-8"?>. LinearLayout. xmlns:android="http://schemas.android.com/apk/res/android"
We need to create the layout to hold our list. Add code inside of the LinearLayout tag so the whole file looks like this: (you may have to hit the Source tab in order to edit the XML file)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ListView android:id="@id/android:list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView android:id="@id/android:empty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/no_notes"/>
</LinearLayout>
ListView and TextView can be
thought as two alternative views, only one of which will be displayed at once.
ListView will be used when there are notes to be shown, while the TextView
(which has a default value of "No Notes Yet!" defined as a string
resource, will be displayed if there aren't any notes to display).And, the android:list and android:empty are
IDs that are already provided for us by the Android platform, empty is used
automatically when no data is provided in the
list adapter. The List Adapter knows to look for these names
specifically by default. Alternatively you could also choose to change the default empty view used by the List Adapter by using the setEmptyView().
More broadly, the android.R class is a set of predefined
resources provided for you by the platform, while your project's
R class is the set of resources your project has defined.
Resources found in the android.R resource class can be
used in the XML files by using the android: name space prefix (as we see here).
The folders under res/ in the Eclipse project are for resources. There is a specific structure to the folders and files under res/.
Resources defined in these folders and files will have corresponding entries in the R class allowing them to be easily accessed and used from your application. The R class is automatically generated using the contents of the res/ folder by the eclipse plugin (or by aapt if you use the command line tools). Furthermore, they will be bundled and deployed for you as part of the application.
To make a list view, we also need to define a view for each row in the list:
res/layout called
notes_row.xml.
<?xml version="1.0" encoding="utf-8"?>
<TextView android:id="@+id/text1"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
text1. The
+ after the @ in the id string indicates that the id should
be automatically created if it does not already exist, so we are defining
text1 on the fly and then using it.R.java class in the
project and look at it, you should see new definitions for
notes_row and text1 (our new definitions)
meaning we can now gain access to these from the our code. Next, open the Notepadv1 class in the source. We are going to
alter this class to become a list adapter and display our notes, and also
allow us to add new notes.
Notepadv1 will inherit from a subclass
of Activity called a ListActivity,
which has extra functionality to accommodate the kinds of
things you might want to do with a list, for
example: displaying an arbitrary number of list items in rows on the screen,
moving through the list items, and allowing them to be selected.
Take a look through the existing code in Notepadv1 class.
There is a currently unused private field called mNoteNumber that
we will use to create numbered note titles
There are also three override methods defined:
onCreate, onCreateOptionsMenu and
onOptionsItemSelected; we need to fill these
out:
onCreate() is called when the activity is
started — it is a little like the "main" method for the activity. We use
this to set up resources and state for the activity when it is
runningonCreateOptionsMenu() is used to populate the
menu for the activity. This is shown when the user hits the menu button,
and
has a list of options they can select (like "Create
Note") onOptionsItemSelected() is the other half of the
menu equation, it is used to handle events generated from the menu (e.g.
when the user selects the "Create Note" item).
Change the inheritance of Notepadv1 from
Activity
to ListActivity:
public class Notepadv1 extends ListActivity
Note: you will have to import ListActivity into the
Notepadv1
class using Eclipse, ctrl-shift-O on Windows or Linux, or
cmd-shift-O on the Mac (organize imports) will do this for you.
Fill out the body of the onCreate() method.
Here we will set the title for the activity (shown at the top of the
screen), use the notepad_list layout we have created for the
activity display contents, set up the NotesDbAdapter instance we will
use to access notes data, then populate the list with the available note
titles:
super() with the icicle parameter passed
into our methodsetContentView to R.layout.notepad_listmDbHelper of class
NotesDbAdapter (before the onCreate method)onCreate method, construct a
NotesDbAdapter
instance — assign to the mDbHelper field (note, you must pass
this into the constructor for DBHelper)open() method on mDbHelper to open (or create) the
database.
fillData()- gets the data and
populates it using the helper, we haven't defined it yet onCreate() should now look like this:
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.notepad_list);
mDbHelper = new NotesDbAdapter(this);
mDbHelper.open();
fillData();
}
And remember to add the mDbHelper field definition (right
under the mNoteNumber definition):
private NotesDbAdapter mDbHelper;
The notepad application we are constructing only scratches the surface with menus.
You can also add shortcut keys for menu items, create submenus and even add menu items to other applications!.
Fill out the body of the onCreateOptionsMenu() method.
We will now create the "Add Item" menu element and specify that it occupy the first position in the menu.
strings.xml resource (under res/values), add
a new string for menu_insert with text "Add Item"
<string name="menu_insert">Add
Item</string>, then save the file
Notepadv1 class:
public static final int INSERT_ID = Menu.FIRST;
onCreateOptionsMenu() method, add the menu item. Also
take care of the result of the super call being returned. The
whole method should now look like this:
@Override
public boolean onCreateOptionsMenu(Menu menu) {
boolean result = super.onCreateOptionsMenu(menu);
menu.add(0, INSERT_ID, R.string.menu_insert);
return result;
}
Fill out the body of the onOptionsItemSelected() method:
This is going
to handle our new "Add Note" menu item. When this is selected, the
onOptionsItemSelected() method will be called with the
item.getId() set to INSERT_ID (the constant we
used to identify the menu item). We can detect this, and take the
appropriate actions:
super.onOptionsItemSelected(item) method call goes at the
end of this method — we want to catch our events first! item.getId()
case INSERT_ID: createNote() return true;
at the end of the case because we have handled this event and do not want to
propagate it through the system.
onOptionsItemSelected()
method at the endonOptionsItemSelect() method should now look like
this:
@Override
public boolean onOptionsItemSelected(Item item) {
switch (item.getId()) {
case INSERT_ID:
createNote();
return true;
}
return super.onOptionsItemSelected(item);
}
Add a new createNote() method:
In this first version of
our application, createNote() is not going to be very useful.
We will simply
create a new note with a title assigned to it based on a counter ("Note 1",
"Note 2"...) and with an empty body. At present we have no way of editing
the contents of a note, so for now we will have to be content making one
with some default values:
String noteName = "Note " + mNoteNumber++; (Construct the name using
"Note" and the counter we have defined in the class) mDbHelper.createNote() using noteName as the
title and "" for the body
fillData() method again after adding (inefficient but
simple) createNote() method should look like this:
private void createNote() {
String noteName = "Note " + mNoteNumber++;
mDbHelper.createNote(noteName, "");
fillData();
}
Our example uses a SimpleCursorAdapter to bind a database Cursor into a ListView, and this is a common way to use a ListAdapter. Other options exist like ArrayAdapter which can be used to take a List or Array of in-memory data and bind it in to a list as well.
Define the fillData() method:
This
method uses SimpleCursorAdapter, which takes a database Cursor,
and binds it into fields provided in the
layout defined for the list row (in this case we use the text1 field in our
notes_row.xml layout).
To do this we have to provide a mapping from the title field in the returned Cursor, to
our text1 view which is done by defining two arrays: the first a string array
with the list of columns to map from (just "title" in this case, from the constant
NotesDbAdapter.KEY_TITLE) and the second an int array
containing references to the views to bind the data into R.id.text1 for us.
private void fillData() {
// Get all of the notes from the database and create the item list
Cursor c = mDbHelper.fetchAllNotes();
startManagingCursor(c);
String[] from = new String[] { NotesDbAdapter.KEY_TITLE };
int[] to = new int[] { R.id.text1 };
// Now create an array adapter and set it to display using our row
SimpleCursorAdapter notes =
new SimpleCursorAdapter(this, R.layout.notes_row, c, from, to);
setListAdapter(notes);
}
mDbHelper.fetchAllNotes() we use a method called
startManagingCursor() from activity to allow Android to take care of the
Cursor lifecycle instead of us needing to worry about it. We will cover the implications
of the lifecycle in exercise 3, but for now just know that this allows Android to do some
of our resource management work for us.this for the context (since subclasses of Activity
implement Context).notes_row view we created as the receptacle
for the data, within that row lies the text1 view that we are
putting the titles in to{ NotesDbAdapter.KEY_TITLE, NotesDbAdapter.KEY_BODY } and
{ R.id.text1, R.id.text2 } to bind two fields into the row (and we would have
to define text2 in the notes_row.xml file as well). This is how you can bind multiple fields
into a single row (and get a custom row layout as well).Run it!
Notepadv1 project Add
Item from the menuYou can see the solution to this class in Notepadv1Solution
from
the zip file to compare with your own.
Once you are ready, move on to Tutorial Exercise 2 to add the ability to create, edit and delete notes.