Skip to content

How does VContentProvider work?

coocood edited this page Dec 13, 2012 · 1 revision

The traditional way to build a ContentProvider is to subclass SQLiteOpenHelper, write SQL commands to create tables, subclass ContentProvider, override all the CRUD method, write a lot of "switch" code with UriMatcher. If you have saw the NotePad sample code, you would have known how tedious it is.

The worst thing is that if you want to change something, like to add a new table, you have to change code in many places. There are too many duplications, not acceptable.

VContentProvider do not use UriMatcher to validate the Uri, because there is a better way to do it. In the manifest.xml file you should have defined the authority for your content provider, so no need to validate the authority part. All you need to do is to make sure the first path segment is a table name you created in the database, and if there is a second path segment, the second path segment can be parsed to a positive long value.

Now, the question is, how do we know if the first path segment is a table name in database?

If you create a table by hand coding SQL, the content provider have no idea what tables you created, therefor you have to pass the table names to the content provider to validate Uris. That's a duplication. But if you create a table with a table factory - VTableCreation, and pass it to content provider, the provider can store the table specs to validate Uris, and generate table creation SQL for you. No need to mention other shortcomings of hand coding SQL.

Create database view for ContentProvider is important, because ContentResolver do not give you the chance to make complex SQL query like "JOIN". So if you want to join tables, the only choice you have is to create a view. Good news is, with VTableCreation you can easily create database view. Just join VTableCreation objects, no duplication at all.

With VContentProvider, you don't need to subclass SQLiteOpenHelper, there is a final class VSQLiteOpenHelper takes care of database creation and upgrade works for you. All you need to do is to implement the "addDatabaseVersionsViewsAndGetName" method in your own content provider that extends VContentProvider. You can create multiple VDatabaseVesrions, so when upgrading, only commands in the newer version will be executed. As views do not contain any data, so they will get dropped and recreated when upgrading.

Starts from Android 2.2, SQLite added foreign key constraint feature, so I recommend to set the mini SDK version to 8 to take advantage of foreign key constraint. If you define a foreign key in a table, VTableCreation will automatically create a index for you, and you can choose cascade or set null on delete. In my case I would always choose cascade.

If your database is synced with a server, then you probably gonna update the local database with JSONObject. With VContentProvider, you don't need to parse the JSONObject and put the values into ContentValues to update the local database, you can just call the static method of your content provider class, pass in the JSONObject, and it will do all the tedious work for you. There is no magic, because your content provider has all the table specs, so it knows how to parse the JSONObject and create ContentValues. But you have to make sure every column name is the same as the key in the JSONObject.

There is only one exception, "_id". Android CursorAdapter requires a table to have a primary key named "_id" to work, so the "_id" column will be created by VTableCreation automatically. But your server side table may not have the "_id" column as primary key. So the constructor of VTableCreation class has a "sourceIdName" parameter to let you define the server side primary key name that can be used to parse JSONObject.

Syncing the local database with a server has a problem that you may encounter primary key conflict when you try to insert data into the table. You can choose the "insert or replace" strategy when conflict occurred, but that is not ideal solution. If your table contains not only the server columns, but also some locally defined columns, "insert or replace" will remove all the local generated data.

VContentProvider's solution for the problem is to always use "update" instead of "insert" if you already have the primary key value. The implementation of "update" is a little different than normal "update", it will try to update first, if the primary key value is given, and 0 row has been affected after update, then it will insert the data into the table, the two steps is wrapped in a transaction, so you will not have conflicts any more.

Update local database with server's data has another problem, that is the foreign key constraint. If you have defined foreign key constraint in a table, SQLite will not allow you add a child table's data without its parent table's data. Sometimes a child table's data without a parent table's data still make sense. But it depends on your application if foreign key constraint is needed.

To comply the foreign key constraint, one way is to put the parent table's data into child table's data, like a twitter status with user info. This way, you can extract the parent table's data and update it first, then update the child table's data. Another way is to request all the parent table's data you need and update them first, then request the related child table's data, but for some application this is not practical. Which way to choose depends on your application.

The two static method to update tables with JSONObject or JSONArray complies foreign key constraint, it ask for a LinkedHashMap parameter called "subJSONObjectMap". If your JSONObject contains any parent table's JSONObject data, you should put the key to the sub JSONObject and the according table name as value into "subJSONObjectMap", The first you put into the map will get updated first. If the JSONObject do not have sub JSONObject, just pass "null" in it.

This library also provided a VCursorAdapter class which subclass CursorAdapter. It is used for making asynchronous query without the need to use support library and implements LoaderCallbacks. It hides the cursor, you don't need to get a cursor first and pass the cursor to the constructor, all you need to do is to make query on it. Make sure you call "changeCursor(null)" in activity's "onDestory()" method to release the resource.

Clone this wiki locally