Creating and Editing Tasks with SQLite

After you have a ContentProvider, you can create a task for it: Insert a record, and then list all tasks on the ReminderListFragment. The user can then tap a task to edit it, or long-press the task to delete it. These user interactions cover the create, read, update, and delete(CRUD) operations needed to make the Task Reminder application work.

Inserting a task entry

Inserting tasks is simple, after you get the hang of it. To insert your first task into the SQLite database, build the Save button click listener to:

1. Retrieve values from EditText views.

2. Store the values to the ReminderProvider database using a ContentResolver.

3. Update the user interface by displaying a toast and closing the edit activity.

After inserting your first task, you should have enough of a grasp on the ReminderProvider class interaction to perform more tasks. The next sections introduce you to the entire implementation of ReminderProvider, which outlines the CRUD operations.

Saving values from the screen to the database

When the user creates a task, it takes place in the OnClickListener of the mConfirmButton of ReminderEditFragment. There, the app responds to the user’s Save button click. If the mRowId for the fragment is 0, the user wants to add a new task. If the mRowId is greater than 0, the user wants to edit an existing task. You first set up some parameters, ask a ContentResolver to complete a create or update, and then process the result and notify the user.

Add the following OnClickListener element to the mConfirmButton in ReminderEditFragment.onCreateView():

mConfirmButton.setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View view) {

ContentValues values = new ContentValues(); →4

values.put(ReminderProvider.COLUMN_ROWID, mRowId); →5

values.put(ReminderProvider.COLUMN_TITLE, mTitleText.getText()

.toString()); →7

values.put(ReminderProvider.COLUMN_BODY, mBodyText.getText()

.toString());

values.put(ReminderProvider.COLUMN_DATE_TIME,

mCalendar.getTimeInMillis()); →11

if (mRowId == 0) { →13

Uri itemUri = getActivity().getContentResolver().insert(

ReminderProvider.CONTENT_URI, values); →15

mRowId = ContentUris.parseId(itemUri); →16

} else {

int count = getActivity().getContentResolver().update(

ContentUris.withAppendedId(

ReminderProvider.CONTENT_URI, mRowId),

values, null, null); →21

if (count != 1) →22

throw new IllegalStateException(“Unable to update “

+ mRowId);

}

Toast.makeText(getActivity(),

getString(R.string.task_saved_message),

Toast.LENGTH_SHORT).show(); →29

getActivity().finish (); →30

}

});

Here’s how the code works:

4 A query is run by ContentResolver that‘s ultimately served by the ReminderProvider. Specifically, this line inserts or updates a reminder. The ContentValues object indicates the record to update and the data to use.

5 The row ID for the reminder — 0 if the user is creating a new record, or the specific ID of the task if the user is updating an existing task.

7 The title of the task, which is the value indicated by the user’s input in the mTitleText EditText view. The app calls mTitle Text.getText() to get a CharSequence and then calls toString() to produce the string.

11 Holds the date and time that the user selected for the reminder. It also calls Calendar.getTimeInMillis() and puts the result in a ContentValues object. (SQLite has no way to represent dates directly, so you must convert the object to a long.)

13 Checks to see whether the user is adding a new task or updating an existing one. If mRowID is 0, the user is adding a new task. Otherwise, the user is updating an existing task.

15 Calls getContentResolver() on the activity and then calls insert() to run the insert query. An insert requires two parameters: the data you want to insert and the URI of the table in which you’re inserting it. In return, insert() gives you the full URI of the data it inserted.

16 Parses the ID of the URI of the item that was inserted, by using ContentUris.parseId(), and sets mRowId to that value.

21 Calls update() on ContentResolver to run an update. Rather than pass in the base URI, it passes the URI of the item being updated, which is obtained by calling ContentUris.with AppendedId() and passing the base URI and the reminder ID. The two last null parameters indicate that no fancy SQL queries need updating more than a single row at a time.

22 Indicates how many records were updated. It should never be anything other than 1, so if no rows or multiple rows are updated, an error message is thrown.

29 Calls Toast.makeText() to display a successful message for a short time. Then it calls show() so that the message appears onscreen.

30 Calls finish() on the activity to close the edit activity and show the list activity again.

The entire ReminderProvider implementation

Sometimes seeing all elements at one time is better than seeing them piecemeal. Working with SQLite in the ReminderProvider class is no different. Listing 12-3 shows the entire implementation of the ReminderProvider so that you can get a feel for what you’re working with.

Listing 12-3: The Full Implementation of ReminderProvider

package com.dummies.android.taskreminder;

import android.content.ContentProvider;

import android.content.ContentResolver;

import android.content.ContentUris;

import android.content.ContentValues;

import android.content.Context;

import android.content.UriMatcher;

import android.database.Cursor;

import android.database.sqlite.SQLiteDatabase;

import android.database.sqlite.SQLiteOpenHelper;

import android.net.Uri;

public class ReminderProvider extends ContentProvider {

// Content Provider Uri and Authority

public static String AUTHORITY = “com.dummies.android.taskreminder.ReminderProvider”;

public static final Uri CONTENT_URI = Uri.parse(“content://” + AUTHORITY

+ “/reminder”);

// MIME types used for searching words or looking up a single definition

public static final String REMINDERS_MIME_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE

+ “/vnd.com.dummies.android.taskreminder.reminder”;

public static final String REMINDER_MIME_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE

+ “/vnd.com.dummies.android.taskreminder.reminder”;

// Database Columns

public static final String COLUMN_ROWID = “_id”;

public static final String COLUMN_DATE_TIME = “reminder_date_time”;

public static final String COLUMN_BODY = “body”;

public static final String COLUMN_TITLE = “title”;

// Database Related Constants

private static final int DATABASE_VERSION = 1;

private static final String DATABASE_NAME = “data”;

private static final String DATABASE_TABLE = “reminders”;

private static final String DATABASE_CREATE = “create table “

+ DATABASE_TABLE + “ (“ + COLUMN_ROWID

+ “ integer primary key autoincrement, “ + COLUMN_TITLE

+ “ text not null, “ + COLUMN_BODY + “ text not null, “

+ COLUMN_DATE_TIME + “ integer not null);”;

// UriMatcher stuff

private static final int LIST_REMINDER = 0;

private static final int ITEM_REMINDER = 1;

private static final UriMatcher sURIMatcher = buildUriMatcher();

private SQLiteDatabase mDb;

/**

* Builds up a UriMatcher for search suggestion and shortcut refresh

* queries.

*/

private static UriMatcher buildUriMatcher() {

UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);

// to get definitions...

matcher.addURI(AUTHORITY, “reminder”, LIST_REMINDER);

matcher.addURI(AUTHORITY, “reminder/#”, ITEM_REMINDER);

return matcher;

}

@Override

public boolean onCreate() {

mDb = new DatabaseHelper(getContext()).getWritableDatabase();

return true;

}

@Override

public Cursor query(Uri uri, String[] ignored1, String ignored2,

String[] ignored3, String ignored4) { →75

String[] projection = new String[] { ReminderProvider.COLUMN_ROWID,

ReminderProvider.COLUMN_TITLE, ReminderProvider.COLUMN_BODY,

ReminderProvider.COLUMN_DATE_TIME }; →79

// Use the UriMatcher to see the query type and format the

// db query accordingly

Cursor c;

switch (sURIMatcher.match(uri)) { →84

case LIST_REMINDER: →85

c = mDb.query(ReminderProvider.DATABASE_TABLE, projection, null,

null, null, null, null); →87

break;

case ITEM_REMINDER: →89

c = mDb.query(ReminderProvider.DATABASE_TABLE, projection, →90

ReminderProvider.COLUMN_ROWID + “=?”, new String[] { Long

.toString(ContentUris.parseId(uri)) }, →92

null, null, null, null);

if (c != null && c.getCount() > 0) { →94

c.moveToFirst(); →95

}

break;

default: →98

throw new IllegalArgumentException(“Unknown Uri: “ + uri);

}

c.setNotificationUri(getContext().getContentResolver(), uri); →102

return c; →103

}

@Override

public Uri insert(Uri uri, ContentValues values) { →107

values.remove(ReminderProvider.COLUMN_ROWID); →108

long id= mDb.insertOrThrow(ReminderProvider.DATABASE_TABLE, null, →109

values);

getContext().getContentResolver().notifyChange(uri, null); →111

return ContentUris.withAppendedId(uri, id); →112

}

@Override

public int delete(Uri uri, String ignored1, String[] ignored2) { →116

int count = mDb.delete(ReminderProvider.DATABASE_TABLE,

ReminderProvider.COLUMN_ROWID + “=?”,

new String[] { Long.toString(ContentUris.parseId(uri)) }); →119

if (count > 0) →120

getContext().getContentResolver().notifyChange(uri, null);

return count; →122

}

@Override

public int update(Uri uri, ContentValues values, String ignored1,

String[] ignored2) { →127

int count = mDb.update(ReminderProvider.DATABASE_TABLE, values,

COLUMN_ROWID + “=?”,

new String[] { Long.toString(ContentUris.parseId(uri)) }); →130

if( count>0 )

getContext().getContentResolver().notifyChange(uri, null); →132

return count; →133

}

/**

* This method is required in order to query the supported types. It’s also

* useful in our own query() method to determine the type of Uri received.

*/

@Override

public String getType(Uri uri) {

switch (sURIMatcher.match(uri)) {

case LIST_REMINDER:

return REMINDERS_MIME_TYPE;

case ITEM_REMINDER:

return REMINDER_MIME_TYPE;

default:

throw new IllegalArgumentException(“Unknown Uri: “ + uri);

}

}

// The SQLite DatabaseHelper code was omitted for brevity.

}

Here’s how the code works:

75 The query() method retrieves a reminder or a list of reminders. Its URI can be either a list type or an item type.

79 Creates a variable named projection that tells various queries which columns of data the user wants to retrieve. Various queries include ID, title, body, date, and time.

84 Calls URIMatcher.match() to determine whether it’s a list URI or an item URI.

85–87 For a list URI, this line calls query() on the SQLiteDatabase to retrieve all columns for all reminders.

The use of the SQLiteDatabase query() method and its parameters is explained in detail in the section “Understanding the query (read) operation,” later in this chapter.

89-90 For an item URI, this line calls query() on the SQLiteDatabase to retrieve all columns for the specified reminder. It specifies the database table and the columns to retrieve.

92 The first parameter is a COLUMN_ROWID=? String, where the value of ? is supplied in the next parameter. The next parameter is an array of strings, where each one maps to a question mark. This parameter has one question mark, so it needs one string in the array, which is the ID of the task (from the URI).

94–95 If the call to the query succeeded and returned at least one value, it moves to the first value. The moveToFirst() method on the Cursor object instructs the cursor to move to the first record in the result set. This method is called only if the Cursor isn’t null. The reason that the cursor isn’t immediately positioned on the first record is that it’s a result set. Before the app can work with the record, it must navigate to record. Think of the result set as a box of items: You can’t work with an item until you take it out of the box.

98 Displays an error message if the URI was neither a list URI nor an item URI.

102 Calls setNotificationUri() on the Cursor object to associate the query result with the URI. It’s passed to the ContentResolver and the URI that was updated.

remember.eps A ContentProvider can notify users of changes in their data. For example, if one fragment has opened a list view for your reminders and another fragment is editing a specific reminder and you save it, the list view is notified that your content has changed and automatically updates it.

103 Returns the Cursor object so that it can be used by the fragment to process the results.

107 The method that’s called when a user inserts a reminder into the database.

108 Removes the ID parameter if it’s there, because you can’t specify an ID when you insert reminders. The database gives you the next available ID.

109 Inserts the reminder into the database using whatever values were passed in.

The insertOrThrow() method usage and its parameters are explained in detail in the “Understanding the insert operation” section, later in this chapter.

111 Calls notifyChange() on the ContentResolver for that URI; the counterpart to the setNotificationUri() call from line 102. When you modify the table by inserting an item, notify anyone who has a query open on that table that the query may have changed.

112 Constructs the final item URI for this reminder by appending to the base URI the ID that was created.

116 The delete method for the ContentProvider.

119 Using the ContentUris.parseId() method to retrieve the task’s id from the URI, calls the delete() method on the SQLite database to delete a task from the database.

The usage and parameters of the delete() method are described in detail in the “Understanding the delete operation” section, later in this chapter.

120 If the database deleted anything, notifies everyone that the database was updated so that they can refresh their queries.

122 Unlike insert(), delete() returns the number of rows that were deleted.

127 The update() method is similar to the delete() method.

130 The update() method is responsible for updating an existing task with new information.

The update() method usage and parameters are explained in detail in the “Understanding the update operation” section, later in this chapter.

132 If the database updated anything, notifies everyone that the database was updated so that they can refresh their queries.

133 Returns the count of rows that were modified.

A CRUD routine accepts a variety of parameters, which are explained in detail in the later sections “Understanding the insert operation,” “Understanding the query (read) operation,” “Understanding the update operation,” and “Understanding the delete operation.”

Understanding the insert operation

The insert operation is simple to complete because you’re simply inserting a value into the database. You call insertOrThrow() instead of insert() because you want to get an exception if a problem occurs while inserting into the database. The insertOrThrow() method accepts these parameters:

check.png table: The name of the table to insert the data into. It uses the DATABASE_TABLE constant for the value.

check.png nullColumnHack: SQL doesn’t allow inserting an empty row, so if the ContentValues parameter (the next parameter) is empty, this column is explicitly assigned a NULL value. It’s passing null for this value.

check.png values: This parameter defines the initial values as defined as a ContentValues object. It’s providing the initialValues local variable as the value for this parameter. This variable contains the key-value pair information for defining a new row.

Understanding the query (read) operation

The query operation is also known as the read operation because most of the time, the application is reading data from the database with the query() method. The query method is responsible for providing a result set based on a list of criteria you provide. This method returns a Cursor that provides random read-write access to the result set returned by the query.

The query method accepts these parameters:

check.png table: The name of the database table to perform the query against. The value comes from the DATABASE_TABLE constant.

check.png columns: A list of columns to return from the query. Passing null returns all columns, which is normally discouraged to prevent reading and returning unnecessary data. If you need all columns, it’s valid to pass null. For this app, you can pass a string array of columns to return.

check.png selection: A filter describing what rows to return and formatted as an SQL WHERE clause (excluding the WHERE itself). Passing a null returns all rows in the table. If it’s a list operation, you supply null because you want all rows returned. If it’s a get operation to return a single reminder, you supply a COLUMN_ROWID=? query string. SQL knows that a question mark indicates a specific value of COLUMN_ROWID in the selectionArgs parameter.

check.png selectionArgs: Permissible to include question marks (?) in the selection string. These marks are replaced by the values from selection Args in the order they appear in the selection. These values are bound as string types. Depending on the situation, you either pass null or a String array containing the ID to be fetched.

check.png groupBy: A filter that shows only rows formatted as an SQL GROUP BY clause (excluding the GROUP BY). Passing null causes the rows not to be grouped. A null value is passed here because grouping the results is unnecessary.

check.png having: A filter that shows only row groups to include in the cursor, if row grouping is being used. Passing null causes all row groups to be included, and it’s required when row grouping isn’t being used, which is why a null value is required here.

check.png orderBy: The order of the rows, formatted as an SQL ORDER BY clause (excluding the ORDER BY itself). Passing null uses the default sort order, which may be unordered. You can pass a null value because the order in which the results are returned is unimportant.

check.png limit: Limits the number of rows returned by the query by using a LIMIT clause. Passing null states that you don’t have a LIMIT clause. To avoid limiting the number of rows returned, you can pass null to return all rows that match your query.

Understanding the update operation

Updating a record in a database simply replaces incoming parameters in the destination cell that’s inside the row specified (or in the rows, if many rows are updated). As in the following delete operation, the update can affect many rows. You should understand the update method’s parameters and how they can affect the records in the database. The update() method accepts these parameters:

check.png table: The table to update. The value is provided by the DATABASE_TABLE constant.

check.png values: The ContentValues object, which contains the fields to update.

check.png whereClause: The WHERE clause, which restricts which rows should be updated. You tell the database to update a row with a specific ID by providing the string value COLUMN_ROWID + “=?”. The ID is specified in whereArgs.

check.png whereArgs: Additional whereClause arguments. You supply the ID that’s plugged into the ? in the whereClause argument. In this case, the whereArgs is a String array containing the ID of the reminder to be updated. There should always be exactly one value in the array for each ? in the whereClause.

Understanding the delete operation

When using the delete() method, various parameters are used to define the deletion criteria in the database. A delete statement can affect none of the records in the database or all of them. You should understand the parameters of the delete call to ensure that you don’t mistakenly delete data. The parameters for the delete() method are

check.png table: The table to delete the rows from. The value of this parameter is provided by the DATABASE_TABLE constant.

check.png whereClause: The optional WHERE clause to apply when deleting rows. If you pass null, all rows are deleted. This value is provided by manually creating the WHERE clause with the string COLUMN_ROWID + “=?”.

check.png whereArgs: The optional WHERE clause arguments. You supply the ID that’s plugged into the ? in the whereClause argument. In this case, whereArgs is a string array containing the ID of the reminder to be deleted. There should always be exactly one value in the array for each ? in the whereClause.

Loaders

When you’re doing any kind of IO operation, such as reading from a network or from disk (reading a database, for example), you must do this work from a background thread. If you work from the main thread of the user interface, you run the risk of locking it up for an unknown period, which can cause it to feel jerky and unresponsive. Under particularly bad circumstances, it can even lead to displaying the dreaded Application Not Responsive dialog box, which can leave many users believing that your application has crashed.

The loader was introduced in Android 3.x to help solve this problem — it provides a mechanism by which you can launch background tasks (such as reading from your database) and then get a callback when those tasks finish so that you can update the user interface.

A typical example of a loader is a CursorLoader. You use a CursorLoader to load data from a SQLite database using a cursor. To add a CursorLoader to one of your list fragments, you implement the LoaderCallback interface in your callback and implement the three LoaderCallback methods:

check.png onCreateLoader(): This method is called in a background thread when you create a loader using initLoader(). In this method, you’re responsible for creating a CursorLoader object and returning it. The CursorLoader uses a URI to ask a ContentProvider for data.

check.png onLoadFinished(): This method is called when the CursorLoader object finishes loading its data from the database. In this method, you’re responsible for updating the UI to show the new data to the user.

check.png onLoaderReset(): This method is called when the loader is being reset or shutdown. When this happens you’re responsible for making sure your fragment no longer uses the loader or its cursor.

To kick off a loader, you first obtain a LoaderManager from your activity by calling getLoaderManager() and then initLoader(). initLoader() starts loading data in the background by calling onCreateLoader(), and when it finishes it executes onLoaderFinished() in your LoaderCall back object.

remember.eps You can use loaders for things other than loading data from a database, but all loaders must implement the same three methods regardless of whether they’re loading their data from a database, a network, or somewhere else entirely.

Visit http://developer.android.com/guide/components/loaders.html for more information about loaders.

Returning all the tasks with a cursor

You can create a task, but what good is it if you can’t see the task in the task list? None, really. You have to list the tasks that currently exist in the database in the ListView in the ReminderListFragment.

Listing 12-4 outlines the entire ReminderListFragment with the new code that can read the list of tasks from the database into the ListView.

Listing 12-4: The Entire ReminderListFragment with Database Access

package com.dummies.android.taskreminder;

import android.database.Cursor;

import android.os.Bundle;

import android.support.v4.app.ListFragment;

import android.support.v4.app.LoaderManager.LoaderCallbacks;

import android.support.v4.content.CursorLoader;

import android.support.v4.content.Loader;

import android.support.v4.widget.SimpleCursorAdapter;

import android.view.View;

import android.widget.ListView;

import com.dummies.android.taskreminder.R.string;

public class ReminderListFragment extends ListFragment implements

LoaderCallbacks<Cursor> { →16

private SimpleCursorAdapter mAdapter;

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

// Create an array to specify the fields to display in the list // (only TITLE)

String[] from = new String[] { ReminderProvider.COLUMN_TITLE }; →26

// and an array of the fields to bind those fields to (in this

// case, just text1)

int[] to = new int[] { R.id.text1 }; →30

// Now create a simple cursor adapter and set it to display

mAdapter = new SimpleCursorAdapter(getActivity(),

R.layout.reminder_row, null, from, to, 0); →34

setListAdapter(mAdapter); →35

getLoaderManager().initLoader(0, null, this); →37

}

@Override

public void onViewCreated(View view, Bundle savedInstanceState) {

super.onViewCreated(view, savedInstanceState);

setEmptyText(getResources().getString(string.no_reminders));

registerForContextMenu(getListView());

setHasOptionsMenu(true);

}

@Override

public void onListItemClick(ListView l, View v, int position, long id) {

super.onListItemClick(l, v, position, id);

startActivity(new Intent(getActivity(), ReminderEditActivity.class) →51

.putExtra(ReminderProvider.COLUMN_ROWID, id));

}

@Override

public boolean onContextItemSelected(MenuItem item) { →56

switch (item.getItemId()) {

case R.id.menu_delete:

AdapterContextMenuInfo info = (AdapterContextMenuInfo) item

.getMenuInfo(); →60

getActivity().getContentResolver().delete(

ContentUris.withAppendedId(ReminderProvider.CONTENT_URI,

info.id), null, null); →63

return true;

}

return super.onContextItemSelected(item);

}

@Override

public Loader<Cursor> onCreateLoader(int ignored, final Bundle args) { →70

return new CursorLoader(getActivity(), ReminderProvider.CONTENT_URI,

null, null, null, null); →71

}

@Override

public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { →75

mAdapter.swapCursor(cursor);

}

@Override

public void onLoaderReset(Loader<Cursor> loader) { →80

// This is called when the last Cursor provided to onLoadFinished()

// above is about to be closed. Ensure that the adapter is no

// longer using the cursor by setting it to null

mAdapter.swapCursor(null);

}

// Menu code removed for brevity

}

This list describes the numbered lines of code for reading the list of tasks:

16 Adds the LoaderCallbacks interface to the list of interfaces.

For this line to work, you implement a few loader-related callbacks later in the class so that you can handle loading data in an asynchronous background thread. See the earlier section “Loaders” for more details.

26 Looks at the title field of the reminder.

30 Defines the array of views to bind to as the view for the row. The title then corresponds to a particular task ID, which is why the variable in line 26 is named from and the variable on this line is named to. The values from line 26 map to the values on line 30.

34 Creates a SimpleCursorAdapter that maps columns from a Cursor to TextViews as defined in a layout XML file. Using this method, you can specify which columns to display and specify the XML file that defines the appearance of these views. Initially, the SimpleCursorAdapter is empty because it has no data yet.

The use of a SimpleCursorAdapter and its associated parameters is described in the following section.

35 Passes the SimpleCursorAdapter as the adapter parameter to the setListAdapter() method to inform the list view where to find its data.

37 Asks the LoaderManager to start loading the loader with an ID of 0. When the loader is finished, it executes the callback methods in the LoaderCallback class that you pass in as the final parameter of this call (in this case, “this”).

51 Places into the intent the ID of the task to be edited. The ReminderEditActivity inspects this intent and, if it finds the ID, attempts to allow the user to edit the task.

56 Defines the method that handles the user context menu events that occur when a user first long-presses the task in the list view and then selects a menu item from the context menu.

60 Uses the getMenuInfo() method of the item that was clicked to obtain an instance of AdapterContextMenuInfo. This class exposes various bits of information about the menu item and the item the user long-pressed in the list view.

63 Gets a ContentResolver and requests that it delete the task whose ID is retrieved from the AdapterContextMenuInfo object’s id field. This field contains the ID of the row in the list view, which is used to construct the URI for the row using ContentUris.withAppendedId(). When the reminder is deleted, the ListView automatically refreshes to show the latest data.

70 When you call initLoader() on line 37, the onCreate Loader() method is called. Here, you create the loader that Android starts running in a background thread. Complicated fragments can have multiple loaders of different types, but you can use a single loader on this simple fragment and safely ignore the ID parameter of the onCreateLoader() method.

71 Creates a new CursorLoader, which is used when you load items from a ContentProvider or SQLiteDatabase. The CursorLoader loads data using the ReminderProvider, so be sure to specify the CONTENT_URI of the ReminderProvider. It’s a simple list operation, so you can ignore the optional projection, selection, selectionArgs, and sortOrder parameters to the CursorLoader constructor.

75 When a loader finishes, calls the onLoadFinished() callback and returns the cursor containing the data that was loaded. Then the adapter swaps the new cursor for the old one and automatically refreshes the ListView.

80 Calls onLoaderReset() when the last Cursor provided to onLoadFinished() is about to be closed. The adapter’s cursor is set to null because it’s no longer needed.

Understanding the SimpleCursorAdapter

Line 34 in Listing 12-4 creates a SimpleCursorAdapter. It does a lot of the work for you when you want to bind data from a Cursor object to a list view. To set up a SimpleCursorAdapter, provide these parameters:

check.png getActivity() - Context: The context associated with the adapter.

check.png R.layout.reminder_row – layout id: The layout resource identifier that defines the file to use for this list item.

check.png null - Cursor: The database cursor that the adapter uses to load data. You have no cursor yet, so use null.

check.png from - from: An array of column names that are used to bind data from the cursor to the view; defined on line 26.

check.png to - to: An array of view IDs that should display the column information from the from parameter. The To field is defined on line 30.

check.png 0 - flags: Any optional flags to be passed to the SimpleCursorAdapter. You don’t need optional flags, so use 0.

The to and from parameters create a map informing the SimpleCursor Adapter how to map data in the cursor to views in the row layout.

When you start the application now, you see a list of items you have created that are being read from the SQLite database. If you don’t see this list, create one by pressing the menu and selecting the menu item to add a new task.

Deleting a task

To the user, deleting a task is as simple as long-pressing an item in the ReminderListFragment and selecting the delete action. To actually delete the task from the database, though, you use the delete() method on the SQLite database object. This method is called in Listing 12-3 on line 119.

The ContentResolver delete () method is called from within the onContextSelectedItem() method call on line 63 of Listing 12-4. The only item that’s needed before deleting the task from the database is the ID of the task in the database. To obtain the ID, you must use the AdapterContextMenuInfo object, which provides extra menu information. This information is provided to the context menu selection when a menu is opened for the ListView. Because you’re loading the list with a database cursor, the ListView contains the ID you’re looking for. Line 60 of Listing 12-4 obtains the AdapterContextMenuInfo object, and line 63 calls the delete() method with the ID as a parameter.

When you fire up the application in the emulator, you can now create, read, update, and delete tasks!

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

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