NullPointerException when linking to Service that uses ContentProvider
- by Danny Chia
H.i everyone, this is my first post here!
Anyways, I'm trying to write a "todo list" application. It stores the data in a ContentProvider, which is accessed via a Service. However, my app crashes at launch. My code is below:
Manifest file:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.examples.todolist"
android:versionCode="1"
android:versionName="1.0">
<application android:icon="@drawable/icon" android:label="@string/app_name" android:debuggable="True">
<activity
android:name=".ToDoList"
android:label="@string/app_name"
android:theme="@style/ToDoTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name="TodoService"/>
<provider android:name="TodoProvider"
android:authorities="com.examples.provider.todolist" />
</application>
<uses-sdk android:minSdkVersion="7" />
</manifest>
ToDoList.java:
package com.examples.todolist;
import com.examples.todolist.TodoService.LocalBinder;
import java.util.ArrayList;
import java.util.Date;
import android.app.Activity;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.ContextMenu;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnKeyListener;
import android.widget.AdapterView;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.Toast;
public class ToDoList extends Activity {
static final private int ADD_NEW_TODO = Menu.FIRST;
static final private int REMOVE_TODO = Menu.FIRST + 1;
private static final String TEXT_ENTRY_KEY = "TEXT_ENTRY_KEY";
private static final String ADDING_ITEM_KEY = "ADDING_ITEM_KEY";
private static final String SELECTED_INDEX_KEY = "SELECTED_INDEX_KEY";
private boolean addingNew = false;
private ArrayList<ToDoItem> todoItems;
private ListView myListView;
private EditText myEditText;
private ToDoItemAdapter aa;
int entries = 0;
int notifs = 0;
//ToDoDBAdapter toDoDBAdapter;
Cursor toDoListCursor;
TodoService mService;
boolean mBound = false;
/** Called when the activity is first created. */
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
myListView = (ListView)findViewById(R.id.myListView);
myEditText = (EditText)findViewById(R.id.myEditText);
todoItems = new ArrayList<ToDoItem>();
int resID = R.layout.todolist_item;
aa = new ToDoItemAdapter(this, resID, todoItems);
myListView.setAdapter(aa);
myEditText.setOnKeyListener(new OnKeyListener() {
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN)
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
ToDoItem newItem = new ToDoItem(myEditText.getText().toString(), 0);
mService.insertTask(newItem);
updateArray();
myEditText.setText("");
entries++;
Toast.makeText(ToDoList.this, "Entry added", Toast.LENGTH_SHORT).show();
aa.notifyDataSetChanged();
cancelAdd();
return true;
}
return false;
}
});
registerForContextMenu(myListView);
restoreUIState();
populateTodoList();
}
private void populateTodoList() {
// Get all the todo list items from the database.
toDoListCursor = mService. getAllToDoItemsCursor();
startManagingCursor(toDoListCursor);
// Update the array.
updateArray();
Toast.makeText(this, "Todo list retrieved", Toast.LENGTH_SHORT).show();
}
private void updateArray() {
toDoListCursor.requery();
todoItems.clear();
if (toDoListCursor.moveToFirst())
do {
String task = toDoListCursor.getString(toDoListCursor.getColumnIndex(ToDoDBAdapter.KEY_TASK));
long created = toDoListCursor.getLong(toDoListCursor.getColumnIndex(ToDoDBAdapter.KEY_CREATION_DATE));
int taskid = toDoListCursor.getInt(toDoListCursor.getColumnIndex(ToDoDBAdapter.KEY_ID));
ToDoItem newItem = new ToDoItem(task, new Date(created), taskid);
todoItems.add(0, newItem);
} while(toDoListCursor.moveToNext());
aa.notifyDataSetChanged();
}
private void restoreUIState() {
// Get the activity preferences object.
SharedPreferences settings = getPreferences(0);
// Read the UI state values, specifying default values.
String text = settings.getString(TEXT_ENTRY_KEY, "");
Boolean adding = settings.getBoolean(ADDING_ITEM_KEY, false);
// Restore the UI to the previous state.
if (adding) {
addNewItem();
myEditText.setText(text);
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
outState.putInt(SELECTED_INDEX_KEY, myListView.getSelectedItemPosition());
super.onSaveInstanceState(outState);
}
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
int pos = -1;
if (savedInstanceState != null)
if (savedInstanceState.containsKey(SELECTED_INDEX_KEY))
pos = savedInstanceState.getInt(SELECTED_INDEX_KEY, -1);
myListView.setSelection(pos);
}
@Override
protected void onPause() {
super.onPause();
// Get the activity preferences object.
SharedPreferences uiState = getPreferences(0);
// Get the preferences editor.
SharedPreferences.Editor editor = uiState.edit();
// Add the UI state preference values.
editor.putString(TEXT_ENTRY_KEY, myEditText.getText().toString());
editor.putBoolean(ADDING_ITEM_KEY, addingNew);
// Commit the preferences.
editor.commit();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
// Create and add new menu items.
MenuItem itemAdd = menu.add(0, ADD_NEW_TODO, Menu.NONE,
R.string.add_new);
MenuItem itemRem = menu.add(0, REMOVE_TODO, Menu.NONE,
R.string.remove);
// Assign icons
itemAdd.setIcon(R.drawable.add_new_item);
itemRem.setIcon(R.drawable.remove_item);
// Allocate shortcuts to each of them.
itemAdd.setShortcut('0', 'a');
itemRem.setShortcut('1', 'r');
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
int idx = myListView.getSelectedItemPosition();
String removeTitle = getString(addingNew ?
R.string.cancel : R.string.remove);
MenuItem removeItem = menu.findItem(REMOVE_TODO);
removeItem.setTitle(removeTitle);
removeItem.setVisible(addingNew || idx > -1);
return true;
}
@Override
public void onCreateContextMenu(ContextMenu menu,
View v,
ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
menu.setHeaderTitle("Selected To Do Item");
menu.add(0, REMOVE_TODO, Menu.NONE, R.string.remove);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
super.onOptionsItemSelected(item);
int index = myListView.getSelectedItemPosition();
switch (item.getItemId()) {
case (REMOVE_TODO): {
if (addingNew) {
cancelAdd();
}
else {
removeItem(index);
}
return true;
}
case (ADD_NEW_TODO): {
addNewItem();
return true;
}
}
return false;
}
@Override
public boolean onContextItemSelected(MenuItem item) {
super.onContextItemSelected(item);
switch (item.getItemId()) {
case (REMOVE_TODO): {
AdapterView.AdapterContextMenuInfo menuInfo;
menuInfo =(AdapterView.AdapterContextMenuInfo)item.getMenuInfo();
int index = menuInfo.position;
removeItem(index);
return true;
}
}
return false;
}
@Override
public void onDestroy() {
super.onDestroy();
}
private void cancelAdd() {
addingNew = false;
myEditText.setVisibility(View.GONE);
}
private void addNewItem() {
addingNew = true;
myEditText.setVisibility(View.VISIBLE);
myEditText.requestFocus();
}
private void removeItem(int _index) {
// Items are added to the listview in reverse order, so invert the index.
//toDoDBAdapter.removeTask(todoItems.size()-_index);
ToDoItem item = todoItems.get(_index);
final long selectedId = item.getTaskId();
mService.removeTask(selectedId);
entries--;
Toast.makeText(this, "Entry deleted", Toast.LENGTH_SHORT).show();
updateArray();
}
@Override
protected void onStart() {
super.onStart();
Intent intent = new Intent(this, TodoService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
// Unbind from the service
if (mBound) {
unbindService(mConnection);
mBound = false;
}
}
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
LocalBinder binder = (LocalBinder) service;
mService = binder.getService();
mBound = true;
}
public void onServiceDisconnected(ComponentName arg0) {
mBound = false;
}
};
public class TimedToast extends AsyncTask<Long, Integer, Integer> {
@Override
protected Integer doInBackground(Long... arg0) {
if (notifs < 15) {
try {
Toast.makeText(ToDoList.this, entries + " entries left", Toast.LENGTH_SHORT).show();
notifs++;
Thread.sleep(20000);
}
catch (InterruptedException e) {
}
}
return 0;
}
}
}
TodoService.java:
package com.examples.todolist;
import android.app.Service;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Intent;
import android.database.Cursor;
import android.os.Binder;
import android.os.IBinder;
public class TodoService extends Service {
private final IBinder mBinder = new LocalBinder();
@Override
public IBinder onBind(Intent arg0) {
return mBinder;
}
public class LocalBinder extends Binder {
TodoService getService() {
return TodoService.this;
}
}
public void insertTask(ToDoItem _task) {
ContentResolver cr = getContentResolver();
ContentValues values = new ContentValues();
values.put(TodoProvider.KEY_CREATION_DATE, _task.getCreated().getTime());
values.put(TodoProvider.KEY_TASK, _task.getTask());
cr.insert(TodoProvider.CONTENT_URI, values);
}
public void updateTask(ToDoItem _task) {
long tid = _task.getTaskId();
ContentResolver cr = getContentResolver();
ContentValues values = new ContentValues();
values.put(TodoProvider.KEY_TASK, _task.getTask());
cr.update(TodoProvider.CONTENT_URI, values, TodoProvider.KEY_ID + "=" + tid, null);
}
public void removeTask(long tid) {
ContentResolver cr = getContentResolver();
cr.delete(TodoProvider.CONTENT_URI, TodoProvider.KEY_ID + "=" + tid, null);
}
public Cursor getAllToDoItemsCursor() {
ContentResolver cr = getContentResolver();
return cr.query(TodoProvider.CONTENT_URI, null, null, null, null);
}
}
TodoProvider.java:
package com.examples.todolist;
import android.content.*;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log;
public class TodoProvider extends ContentProvider {
public static final Uri CONTENT_URI = Uri.parse("content://com.examples.provider.todolist/todo");
@Override
public boolean onCreate() {
Context context = getContext();
todoHelper dbHelper = new todoHelper(context, DATABASE_NAME, null, DATABASE_VERSION);
todoDB = dbHelper.getWritableDatabase();
return (todoDB == null) ? false : true;
}
@Override
public Cursor query(Uri uri, String[] projection,
String selection, String[] selectionArgs, String sort) {
SQLiteQueryBuilder tb = new SQLiteQueryBuilder();
tb.setTables(TODO_TABLE);
// If this is a row query, limit the result set to the passed in row.
switch (uriMatcher.match(uri)) {
case TASK_ID:
tb.appendWhere(KEY_ID + "=" + uri.getPathSegments().get(1));
break;
default:
break;
}
// If no sort order is specified sort by date / time
String orderBy;
if (TextUtils.isEmpty(sort)) {
orderBy = KEY_ID;
} else {
orderBy = sort;
}
// Apply the query to the underlying database.
Cursor c = tb.query(todoDB, projection, selection, selectionArgs, null, null, orderBy);
// Register the contexts ContentResolver to be notified if
// the cursor result set changes.
c.setNotificationUri(getContext().getContentResolver(), uri);
// Return a cursor to the query result.
return c;
}
@Override
public Uri insert(Uri _uri, ContentValues _initialValues) {
// Insert the new row, will return the row number if
// successful.
long rowID = todoDB.insert(TODO_TABLE, "task", _initialValues);
// Return a URI to the newly inserted row on success.
if (rowID > 0) {
Uri uri = ContentUris.withAppendedId(CONTENT_URI, rowID);
getContext().getContentResolver().notifyChange(uri, null);
return uri;
}
throw new SQLException("Failed to insert row into " + _uri);
}
@Override
public int delete(Uri uri, String where, String[] whereArgs) {
int count;
switch (uriMatcher.match(uri)) {
case TASKS:
count = todoDB.delete(TODO_TABLE, where, whereArgs);
break;
case TASK_ID:
String segment = uri.getPathSegments().get(1);
count = todoDB.delete(TODO_TABLE, KEY_ID + "=" + segment
+ (!TextUtils.isEmpty(where) ? " AND (" + where + ')' : ""), whereArgs);
break;
default: throw new IllegalArgumentException("Unsupported URI: " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
@Override
public int update(Uri uri, ContentValues values, String where, String[] whereArgs) {
int count;
switch (uriMatcher.match(uri)) {
case TASKS: count = todoDB.update(TODO_TABLE, values,
where, whereArgs);
break;
case TASK_ID: String segment = uri.getPathSegments().get(1);
count = todoDB.update(TODO_TABLE, values, KEY_ID
+ "=" + segment + (!TextUtils.isEmpty(where) ? " AND ("
+ where + ')' : ""), whereArgs);
break;
default: throw new IllegalArgumentException("Unknown URI " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
@Override
public String getType(Uri uri) {
switch (uriMatcher.match(uri)) {
case TASKS: return "vnd.android.cursor.dir/vnd.examples.task";
case TASK_ID: return "vnd.android.cursor.item/vnd.examples.task";
default: throw new IllegalArgumentException("Unsupported URI: " + uri);
}
}
// Create the constants used to differentiate between the different URI
// requests.
private static final int TASKS = 1;
private static final int TASK_ID = 2;
private static final UriMatcher uriMatcher;
// Allocate the UriMatcher object, where a URI ending in 'tasks' will
// correspond to a request for all tasks, and 'tasks' with a
// trailing '/[rowID]' will represent a single task row.
static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI("com.examples.provider.Todolist", "tasks", TASKS);
uriMatcher.addURI("com.examples.provider.Todolist", "tasks/#", TASK_ID);
}
//The underlying database
private SQLiteDatabase todoDB;
private static final String TAG = "TodoProvider";
private static final String DATABASE_NAME = "todolist.db";
private static final int DATABASE_VERSION = 1;
private static final String TODO_TABLE = "todolist";
// Column Names
public static final String KEY_ID = "_id";
public static final String KEY_TASK = "task";
public static final String KEY_CREATION_DATE = "date";
public long insertTask(ToDoItem _task) {
// Create a new row of values to insert.
ContentValues newTaskValues = new ContentValues();
// Assign values for each row.
newTaskValues.put(KEY_TASK, _task.getTask());
newTaskValues.put(KEY_CREATION_DATE, _task.getCreated().getTime());
// Insert the row.
return todoDB.insert(TODO_TABLE, null, newTaskValues);
}
public boolean updateTask(long _rowIndex, String _task) {
ContentValues newValue = new ContentValues();
newValue.put(KEY_TASK, _task);
return todoDB.update(TODO_TABLE, newValue, KEY_ID + "=" + _rowIndex, null) > 0;
}
public boolean removeTask(long _rowIndex) {
return todoDB.delete(TODO_TABLE, KEY_ID + "=" + _rowIndex, null) > 0;
}
// Helper class for opening, creating, and managing database version control
private static class todoHelper extends SQLiteOpenHelper {
private static final String DATABASE_CREATE =
"create table " + TODO_TABLE + " ("
+ KEY_ID + " integer primary key autoincrement, "
+ KEY_TASK + " TEXT, "
+ KEY_CREATION_DATE + " INTEGER);";
public todoHelper(Context cn, String name, CursorFactory cf, int ver) {
super(cn, name, cf, ver);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(DATABASE_CREATE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
+ newVersion + ", which will destroy all old data");
db.execSQL("DROP TABLE IF EXISTS " + TODO_TABLE);
onCreate(db);
}
}
}
I've omitted the other files as I'm sure they are correct. When I run the program, LogCat shows that the NullPointerException occurs in populateTodoList(), at toDoListCursor = mService.getAllToDoItemsCursor(). mService is the Cursor object returned by TodoService. I've added the service to the Manifest file, but I still cannot find out why it's causing an exception.
Thanks in advance.