Joe Asks

^ Why Is Constants an Interface?

It's a Java thing. I don't know about you, but I dislike having to repeat the class name every time I use a constant. For example, I want to just type TIME and not Constants.TIME. Traditionally, the way to do that in Java is to use interfaces. Classes can inherit from the Constants interface and then leave out the interface name when referencing any fields. If you look at the BaseColumns interface, you'll see the Android programmers used the same trick.

Starting with Java 5, however, there's a better way: static imports. That's the method I'll use in EventsData and other classes in this chapter. Since Constants is an interface, you can use it the old way or the new way as you prefer.

Unfortunately, as of this writing, Eclipse's support for static imports is a little spotty, so if you use static imports in your own programs, Eclipse may not insert the import statements for you automatically. Here's a little trick for Eclipse users: type a wildcard static import after the package statement (for example, import static org.example.events.Constants.*;) to make things compile. Later, you can use Source > Organize Imports to expand the wildcard and sort the import statements. Let's hope this will be more intuitive in future versions of Eclipse.

Define the layout file (layout/main.xml) as follows:

Download Eventsv1/res/layout/main.xml

<?xml version="1.0" encoding="utf-8"?>

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width= "fill_parent" android:layout_height="fill_parent"> <TextView android:id="@+id/text" android:layout_width= "fill_parent" android:layout_height= "'wrap_content" /> </ScrollView>

This declares the TextView with an imaginative ID of text (R.id.text in code) and wraps it with a ScrollView in case there are too many events to fit on the screen. You can see how it looks in Figure 9.2, on the next page.

Events

Saved events:

3:1248132542597: Hello. Android! 2:1248132534063: Hello, Android! 1:1248132516477: Hello. Android!

Figure 9.2: The first version displays database records in a TextView.

The main program is the onCreate() method in the Events activity. Here's the outline:

Download Eventsv1/src/org/example/events/Events.java Line 1 package org.example.events;

- import static android.provider.BaseColumns._ID;

- import static org.example.events.Constants.TABLE_NAME; 5 import static org.example.events.Constants.TIME;

- import static org.example.events.Constants.TITLE;

- import android.app.Activity;

- import android.content.ContentValues;

- import android.database.Cursor;

10 import android.database.sqlite.SQLiteDatabase;

- import android.os.Bundle;

- import android.widget.TextView;

- public class Events extends Activity { 15 private EventsData events;

©Override

- public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState); 20 setContentView(R.layout.main);

- events = new EventsData(this);

- addEvent("Hello, Android!"); Cursor cursor = getEvents();

25 showEvents(cursor);

events.close();

On line 20 of onCreate(), we set the layout for this view. Then we create an instance of the EventsData class on line 21 and start a try block. If you look ahead to line 27, you can see we close the database inside the finally block. So even if an error occurs in the middle, the database will still be closed.

The events table wouldn't be very interesting if there weren't any events, so on line 23 we call the addEvent() method to add an event to it. Every time you run this program, you'll get a new event. You could add menus or gestures or keystrokes to generate other events if you like, but I'll leave that as an exercise to the reader.

On line 24, we call the getEvents() method to get the list of events, and finally on line 25, we call the showEvents() method to display the list to the user.

Pretty easy, eh? Now let's define those new methods we just used. Adding a Row

The addEvent( ) method cuts a new record in the database using the string provided as the event title.

Download Eventsv1/src/org/example/events/Events.java

private void addEvent(String string) {

// Insert a new record into the Events data source. // You would do something similar for delete and update. SQLiteDatabase db = events.getWritableDatabaseO; ContentValues values = new ContentValues(); values.put(TIME, System.currentTi'meMi'llisO); values.put(TITLE, string); db.insertOrThrow(TABLE_NAME, null, values);

Since we need to modify the data, we call getWritableDatabase() to get a read/write handle to the events database. The database handle is cached, so you can call this method as many times as you like.

Next we fill in a ContentValues object with the current time and the event title and pass that to the insertOrThrow( ) method to do the actual INSERT SQL statement. You don't need to pass in the record ID because SQLite will make one up and return it from the method call.

As the name implies, insertOrThrow() can throw an exception (of type SQLException) if it fails. It doesn't have to be declared with a throws keyword because it's a RuntimeException and not a checked exception. However, if you want to, you can still handle it in a try/catch block like any other exception. If you don't handle it and there is an error, the program will terminate, and a traceback will be dumped to the Android log.

By default, as soon as you do the insert, the database is updated. If you need to batch up or delay modifications for some reason, consult the SQLite website for more details.

Running a Query

The getEvents( ) method does the database query to get a list of events:

Download Eventsv1/src/org/example/events/Events.java

private static String[] FROM = { _ID, TIME, TITLE, }; private static String ORDER_BY = TIME + " DESC"; private Cursor getEvents() {

// Perform a managed query. The Activity will handle closing // and re-querying the cursor when needed. SQLiteDatabase db = events.getReadableDatabase(); Cursor cursor = db.query(TABLE_NAME, FROM, null, null, null, null, ORDER_BY); startManagingCursor(cursor); return cursor;

We don't need to modify the database for a query, so we call getRead-ableDatabase() to get a read-only handle. Then we call query() to perform the actual SELECT SQL statement. FROM is an array of the columns we want, and ORDER_BY tells SQLite to return the results in order from newest to oldest.

Although we don't use them in this example, the query( ) method has parameters to specify a WHERE clause, a GROUP BY clause, and a HAVING clause. Actually, query() is just a convenience for the programmer. If you prefer, you could build up the SELECT statement yourself in a string and use the rawQuery() method to execute it. Either way, the return value is a Cursor object that represents the result set.

A Cursor is similar to a Java Iterator or a JDBC ResultSet. You call methods on it to get information about the current row, and then you call another method to move to the next row. We'll see how to use it when we display the results in a moment.

The final step is to call startManagingCursor( ), which tells the activity to take care of managing the cursor's life cycle based on the activity's life cycle. For example, when the activity is paused, it will automatically deactivate the cursor and then requery it when the activity is restarted. When the activity terminates, all managed cursors will be closed.

Report erratum

Displaying the Query Results

The last method we need to define is showEvents(). This function takes a Cursor as input and formats the output so the user can read it.

Download Eventsv1/src/org/example/events/Events.java

Line 1 private void showEvents(Cursor cursor) {

- // Stuff them all into a big string StringBuilder builder = new StringBuilder(

5 while (cursor.moveToNext()) {

- // Could use getColumnIndexOrThrow() to get indexes

- long id = cursor.getLong(O); long time = cursor.getLong(l); String title = cursor.getString(2);

- builder.append(time).append(": "); builder.append(title).append("\n");

15 TextView text = (TextView) findViewById(R.id.text);

In this version of Events, we're just going to create a big string (see line 3) to hold all the events items, separated by newlines. This is not the recommended way to do things, but it'll work for now.

Line 5 calls the Cursor.moveToNext() method to advance to the next row in the data set. When you first get a Cursor, it is positioned before the first record, so calling moveToNext() gets you to the first record. We keep looping until moveToNext() returns false, which indicates there are no more rows.

Inside the loop (line 7), we call getLong() and getString() to fetch data from the columns of interest, and then we append the values to the string (line 10). There is another method on Cursor, getColumnlndex-OrThrow(), that we could have used to get the column index numbers (the values 0, 1, and 2 passed to getLong() and getString()). However, it's a little slow, so if you need it, you should call it outside the loop and remember the indexes yourself.

Once all the rows have been processed, we look up the TextView from layout/main.xml and stuff the big string into it (line 15).

If you run the example now, you should see something like Figure 9.2, on page 185. Congratulations on your first Android database program! There is plenty of room for improvement, though.

Report erratum

What would happen if there were thousands or millions of events in the list? The program would be very slow and might run out of memory trying to build a string to hold them all. What if you wanted to let the user select one event and do something with it? If everything is in a string, you can't do that. Luckily, Android provides a better way: data binding.

0 0

Post a comment