views:

77

answers:

3

I'm trying to implement a ContentProvider wrapped around an SQLite database.

I've followed the tutorial here in building my ContentProvider: tutorial

I want to test what I have; so I'm attempting to instantiate my new ContentProvider, retrieve a Cursor from the query handler, and attach it to my CursorAdapter. Currently, I'm doing this in the onCreate of my Activity (I know this is bad practice, I'm just testing, I will move it to a service eventually).

Uri uri = Uri.parse("content://com.test.db.providers.Messages/messages");
String s[] = {"_id", "delivery_id", "user_id", "created_on", "subject", "summary", "messagetext", "read", "status"};
MessagesProvider p = new MessagesProvider();
if (p.open()) {
   Cursor messages = p.query(uri, s, null, null, null);
   startManagingCursor(messages);
}

When I launch my application, the onCreate method of my extended ContentProvider gets executed. The database helper object gets created, the database gets created, and the onCreate method returns true. However, when I try to use my ContentProvider (with the code above), in the open() method the database helper object gets created, but getWritableDatabase() returns null. Also, when I call open(), the reference to getContext() is null.

Note: everything else seems to be working fine. When I call query, it hits my query handler, recognizes the Uri and attempts to run my query code (which obviously blows up because the database object is null).

Here are my extended ContentProvider and database helper:

package com.test.db.providers;

import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;

import com.test.db.DbDefinitions;
import com.test.db.DbHelper;

public class MessagesProvider extends ContentProvider {

 private DbHelper mDbHelper;
    private SQLiteDatabase mDb;
    private static final UriMatcher sUriMatcher;

    private static final String PROVIDER_NAME = "com.test.db.providers.Messages";
    private static final Uri CONTENT_URI = Uri.parse("content://" + PROVIDER_NAME + "/messages");

 public static final String id = "_id";
 public static final String delivery_id = "delivery_id";
 public static final String user_id = "user_id";
 public static final String created_on = "created_on";
 public static final String subject = "subject";
 public static final String summary = "summary";
 public static final String messagetext = "messagetext";
 public static final String status = "status";

    private static final int MESSAGES = 1;
    private static final int MESSAGES_ID = 2;

    static {
     sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
     sUriMatcher.addURI(PROVIDER_NAME, "messages", MESSAGES);
     sUriMatcher.addURI(PROVIDER_NAME, "messages/#", MESSAGES_ID);
    }

    public boolean open() {
        mDbHelper = new DbHelper(getContext());
        mDb = mDbHelper.getWritableDatabase();
        return (mDb == null) ? false : true;
    }
    public void close() {
     mDbHelper.close();
    }


    @Override
    public boolean onCreate () {
     mDbHelper = new DbHelper(getContext());
     mDb = mDbHelper.getWritableDatabase();
     return (mDb == null) ? false : true;
    }

    @Override
    public String getType (Uri uri) {
     switch (sUriMatcher.match(uri)) {
      case MESSAGES:
       return "vnd.android.cursor.dir/com.test.messages";
      case MESSAGES_ID:
       return "vnd.android.cursor.item/com.test.messages";
      default:
       throw new IllegalArgumentException("Unknown URI " + uri);
     }
    }

    @Override
    public Cursor query (Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
     switch (sUriMatcher.match(uri)) {
      case MESSAGES:
       return queryMessages(uri, projection, selection, selectionArgs, sortOrder);
      default:
       throw new IllegalArgumentException("Unknown Uri " + uri);
     }
    }

    @Override
    public Uri insert (Uri uri, ContentValues initialValues) {
     switch (sUriMatcher.match(uri)) {
   case MESSAGES:
    return insertMessages(uri, initialValues);
   default:
    throw new IllegalArgumentException("Unknown URI " + uri);
     }
    }

    @Override
    public int update (Uri uri, ContentValues values, String selection, String[] selectionArgs) {
     switch (sUriMatcher.match(uri)) {
      case MESSAGES:
       return updateMessages(uri, values, selection, selectionArgs);
      default:
       throw new IllegalArgumentException("Unknown URI " + uri);
     }
    }

    @Override
    public int delete (Uri uri, String selection, String[] selectionArgs) {
     switch (sUriMatcher.match(uri)) {
   case MESSAGES:
    return deleteMessages(uri, selection, selectionArgs);
   default:
    throw new IllegalArgumentException("Unknown URI " + uri);
     }
    }


    /*
     * Messages
     */
    private Cursor queryMessages(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
     Cursor c = mDb.query(DbDefinitions.TABLE_MESSAGES, projection, selection, selectionArgs, null, null, sortOrder);
     if (c != null) {
      c.moveToFirst();
     }
     return c;
    }

    private Uri insertMessages(Uri uri, ContentValues initialValues) {
  ContentValues values;
  if (initialValues != null)
   values = new ContentValues(initialValues);
  else
   values = new ContentValues();
  long rowId = mDb.insert(DbDefinitions.TABLE_MESSAGES, summary, values);
  if (rowId > 0) {
   Uri messageUri = ContentUris.withAppendedId(CONTENT_URI, rowId);
   getContext().getContentResolver().notifyChange(messageUri, null);
   return messageUri;
  }
  throw new SQLException("Failed to insert new message " + uri);
    }

    private int updateMessages(Uri uri, ContentValues values, String where, String[] whereArgs) {
     int result = mDb.update(DbDefinitions.TABLE_MESSAGES, values, where, whereArgs);
     getContext().getContentResolver().notifyChange(uri, null);
     return result;
    }

    public int deleteMessages(Uri uri, String where, String[] whereArgs) {
     // TODO flag message as deleted
     return 0;
    }
}



package com.test.db;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class DbHelper extends SQLiteOpenHelper {

 public DbHelper(Context context) {
  super(context, DbDefinitions.DATABASE_NAME, null, DbDefinitions.DATABASE_VERSION);
 }

 @Override
    public void onCreate(SQLiteDatabase db) {
     db.execSQL(DbDefinitions.DB_CREATE);
     db.execSQL(DbDefinitions.DB_TEST_DATA);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
     // TODO run upgrade string
     db.execSQL("DROP TABLE IF EXISTS " + DbDefinitions.TABLE_MESSAGES);
     onCreate(db);
    }
}

I'm wondering if I should somehow be referencing whatever instance of MessagesProvider was created when I launched the application, instead of declaring a new one (p) and using it?

I updated the onCreate code in my Activity to the following, but managedQuery returned null:

Uri uri = Uri.parse("content://com.buyercompass.app.provider.Messages/messages");
String s[] = {"_id", "delivery_id", "user_id", "created_on", "subject", "summary", "messagetext", "read", "status"};

Cursor messages = managedQuery(uri, s, null, null, null);
if (messages != null)
    startManagingCursor(messages);

ExampleCursorAdapter msg = 
    new ExampleCursorAdapter(this, messages);
setListAdapter(msg);
A: 

can you paste more of your code, specifically, the over-ride of ContentProvider with the onCreate method?

ddopson
I've updated the main post
Andrew
A: 

Did you register your provider ?

fedj
Yes, I have placed the following inside the <manifest> tag just before the <application> tag: <provider android:name="com.test.db.providers.MessagesProvider" android:authorities="com.test.db.providers.MessagesProvider" />
Andrew
I've also attempted to move the provider tag inside the application tag. When I attempt to launch the application, the application crashes (before reaching my Activity that instantiates the provider. The log says there's a syntax error here: create table messages (_id integer primary key), delivery_id integer, user_id integer not null, created_on integer not null, subject text not null, summary text not null, messagetext text not null, read integer not null, status text not null default 'STATE_OK';
Andrew
Yikes, caught the syntax error. Misplaced )
Andrew
Anyway. Provider is properly registering now. The onCreate method of my extended ContentProvider is executing, but my database helper instantiation is failing
Andrew
+1  A: 

Before writing anything else: take a look at the Notepad example from the Android Developer website. In my opinion it is a great example for seing how ContentProviders are being implemented.

After having studied that example, I would stick on the way they write ContentProviders there and also on how they invoke it from the UI for retrieving data.

For instance you won't need the "open()" method. What you can do on your Activity is simply

@Override
public void onCreate(Bundle savedInstanceState){
   ...

   if (getIntent().getData() == null) {
       getIntent().setData(MyMetaData.CONTENT_URI);
   }

   Cursor cursor = managedQuery(getIntent().getData(), null, null, null, null);

   //create an appropriate adapter and bind it to the UI
   ...
}

this will automatically call the ContentProvider that is able to handle the given content uri, given you registered it in the manifest.xml file like

<provider android:name=".provider.MyContentProvider" android:authorities="com.mycompany.contentprovider.MyContentProvider" />

//Just as a sidenote Since I prefer having automated unit tests in place and you mention you'd just like to test whether your ContentProvider actually works, you may also consider to write a unit test against it, more specifically a ProviderTestCase2:

public class MessagesProviderTest extends ProviderTestCase2<MessagesProvider> {
    private MockContentResolver mockResolver;

    public MessagesProviderTest() {
        super(MessagesProvider.class, MessagesMetaData.AUTHORITY);
    }

    @Override
    protected void setUp() throws Exception {
        super.setUp();
        mockResolver = getMockContentResolver();
    }

    @Override
    protected void tearDown() throws Exception {
        super.tearDown();
        mockResolver = null;

            //clean the old db
        getContext().getDatabasePath("test.messages.db").delete();
    }

    public void testRetrieveMessages() {
        //TODO insert some using ContentValues

        //try to retrieve them
        Cursor readMessagesCursor = mockResolver.query(....);
        assertTrue("The cursor should contain some entries", readMessagesCursor.moveToFirst());


           ...
    }
 }

This just as a sidenote, but I do really recommend this, because in this way you can

  1. Test whether your ContentProvider works without having to implement some dummy Activity, Service or whatever
  2. You can always rerun the test after you modify your ContentProvider implementation and see whether you didn't break anything.
Juri
Thanks for your response. This is essentially what I have now (I've updated my post). However, managedQuery returns null and I'm not sure why. I've added the following tag to my application tag in the manifest and the log seems to show proper registration of the provider: <provider android:name="com.test.db.providers.MessagesProvider" android:authorities="com.test.db.providers.MessagesProvider" />
Andrew
AH HAH! Ok, I'm doing better, but my query handler doesn't recognize the Uri for some reason. Let me make sure I haven't mistyped anything
Andrew
From your example you posted, it seems as if the URI in the ContentProvider is different from the one used in the Activity. Probably you just didn't update the post correctly.
Juri
Lord have mercy. It's working! ... ish. The first row in my ListView automatically has focus and I don't really like that.
Andrew
Fyi, I was watching a Google I/O presentation, I believe it was the one about REST services, and I believe the presenter stated that database transactions should be called by services and web services should be called from threads launched by services.
Andrew