Home > Android, Bruce's Posts, Freebase > Quotation Content Provider

Quotation Content Provider

It’s time to have my quotation content provider to take direct advantage of Freebase’s . Here are a list of provider requirements:

  1. It must always have content to provide. It cannot rely on fetching content via a network. It therefore must have some initialization data when the application is installed. 
  2. As quotations are consumed the provider will replace those quotations with new ones from Freebase.

This iteration will implement the initial connections to Freebase. Later version will refine it.

The provider will use Android’s SQLite to cache the content. I’ll start out with one table that holds the quotation, author’s name, and URL to author’s image. Later, as more data is needed, I’ll add additional tables and normalize the model.

After taking a look at android.provider.ContactsContract I decided to mimic this pattern. QuotationContract describes the  quotation table and how to use a Uri to reference a row in the table.

Every quotation in Freebase has a unique id so we’ll use that id as the primary key in the table. This means the Uri for a quotation is the authority Uri plus the quotation table name plus the free base id. This Uri becomes the standard way to identify a quotation. It will be used throughout the code. For example …

content://org.bwgz.quotation/quotation/quotationsbook/quote/8185

references a quotation from Ralph Waldo Emerson.

A foolish consistency is the hobgoblin of little minds, adored by little statesmen and philosophers and divines.

public final class QuotationContract {
	public static final String AUTHORITY = "org.bwgz.quotation";
	public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY);

	protected interface StatusColumns {
		public enum State {
			UNINITIALIZED(0), INITIALIZED(1);

		    private final int value;
		    State(int value) {
		        this.value = value;
		    }
		    public int getValue() {
		    	return value;
		    }
		}

		public static final String STATE		= "state";
		public static final String MODIFIED		= "modified";
	}

	protected interface QuotationColumns {
		public static final String QUOTATION	= "quotation";
		public static final String AUTHOR_NAME	= "author_name";
		public static final String AUTHOR_IMAGE	= "author_image";
	}

	public static class Quotation implements BaseColumns, StatusColumns, QuotationColumns {
		public static final String TABLE = "quotation";

		private Quotation() {
		}

		public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, TABLE);

		public static Uri getUri(String id) {
			return Uri.parse(CONTENT_URI + id);
		}

		public static String getId(Uri uri) {
	    	return uri.getPath().substring(Quotation.CONTENT_URI.getPath().length(), uri.getPath().length());
		}

		/**
		 * The MIME type of {@link #CONTENT_URI} providing a directory of
		 * quotations.
		 */
		public static final String CONTENT_TYPE = "vnd.android.cursor.dir/quotation";

		/**
		 * The MIME type of a {@link #CONTENT_URI} a single quotation.
		 */
		public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/quotation";

	}
}

QuotationSQLiteHelper, an extension of SQLiteOpenHelper, provides methods to create the database, initialize it, and CRUD operations.

QuotationSQLiteHelper, an extension of SQLiteOpenHelper, provides methods to create the database, initialize it, and CRUD operations. When the database is created it is also initializes the quotation table from a CSV file containing quotes and their Freebase id.  The author name and image URL are not initialized. We’ll use a lazy fetch to get those when needed. The state column is set to uninitialized because those fields aren’t set.

QuotationSyncAdapter.onPerformSync now expects to be passed the Uri of quotation requiring synchronization. It parses the Freebase quotation id from the Uri, builds a Freebase query, and executes it. If the query is successful it will update the table using a batch operation that calls the provider’s update method. If that update is successful the provider will notify any objects that had requested notification of changes to that Uri.

	private MQLQueryBuilder builder = new MQLQueryBuilder();

	private Quotation findQuotationFromFreebase(String id) {
		Quotation quotation = new Quotation();

		quotation.setId(id);
		String query = builder.createQuery(Quotation.class, null, quotation);
		Log.d(TAG, String.format("query: %s", query));

		FreebaseQuery fbQuery = new FreebaseQuery();
		org.bwgz.freebase.model.Quotation result = fbQuery.getResult(query, Quotation.class);
		Log.d(TAG, String.format("result: %s\n", result));

		return result;
	}

	@Override
	public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
		Log.d(TAG, String.format("onPerformSync - account: %s  extras: %s  authority: %s  provider: %s  syncResult: %s", account, extras, authority, provider, syncResult));

		for (String key : extras.keySet()) {
			Log.d(TAG, String.format("%s: %s", key, extras.get(key)));
		}

		String string = extras.getString(SYNC_EXTRAS_QUOTATION_UPDATE);
		if (string != null) {
			Uri uri = Uri.parse(string);
	    	String _id = org.bwgz.quotation.content.provider.QuotationContract.Quotation.getId(uri);
			Log.d(TAG, String.format("_id: %s", _id));

			Quotation quotation = findQuotationFromFreebase(_id);
			if (quotation != null) {
				ArrayList operationList = new ArrayList();

				ContentProviderOperation.Builder builder = ContentProviderOperation.newUpdate(uri);
				builder.withValue(org.bwgz.quotation.content.provider.QuotationContract.Quotation.QUOTATION, quotation.getName());
				builder.withValue(org.bwgz.quotation.content.provider.QuotationContract.Quotation.AUTHOR_NAME, quotation.getAuthor().getName());
				builder.withValue(org.bwgz.quotation.content.provider.QuotationContract.Quotation.AUTHOR_IMAGE, quotation.getAuthor().getId());
				builder.withSelection("_id = ?", new String[] { _id });
				operationList.add(builder.build());

				try {
					getContext().getContentResolver().applyBatch(QuotationContract.AUTHORITY, operationList);
				} catch (RemoteException e) {
					e.printStackTrace();
				} catch (OperationApplicationException e) {
					e.printStackTrace();
				}
			}
		}
	}

When QuoteActivity resumes it determine which quotation, using the quotation’s Uri, it should be displaying. If the quotation has not be initialized it will:

  1. Display a “waiting” message.
  2. Register a ContentObserver for the Uri.
  3. Request that the content provider update (sync) the quotation.

The sync request is non-blocking. The onResume method returns and the application continues to run normally. Meanwhile, the sync request runs in the background and if successful the ContentObserver (QuotationContentObserver) will be notified by the provider when the table has been updated. QuotationContentObserver then fetches the quotation from the database and updates the display accordingly.

	class QuotationContentObserver extends ContentObserver {
		private Uri uri;

		public QuotationContentObserver() {
	        super(new Handler());
	    }

		public Uri getUri() {
			return uri;
		}

		public void setUri(Uri uri) {
			this.uri = uri;
		}

	    @Override
	    public void onChange(boolean selfChange) {
	        super.onChange(selfChange);
	        Log.d(TAG, "QuoteContentObserver.onChange( " + selfChange + ")");

	        String quotation = getQuotation(uri);
	        if (quotation != null) {
	        	setQuote(quotation);
	        }

	        String author = getAuthor(uri);
	        if (author != null) {
	        	setAuthor(author);
	        }

	        String authorImage = getAuthorImage(uri);
	        if (authorImage != null) {
	        	setAuthorImage(authorImage);
	        }
	    }
	}

    ...

    protected void onResume() {
    	super.onResume();
		Log.d(TAG, String.format("onResume"));

		Intent intent = getIntent();
		Uri uri = intent.getData();
		Log.d(TAG, String.format("intent: %s  uri: %s", intent, uri));

		if (uri == null) {
			uri = getRandomQuotationUri();
			Log.d(TAG, String.format("random uri: %s", uri));
		}

		if (isQuotationInitialized(uri)) {
			setQuote(getQuotation(uri));
			setAuthor(getAuthor(uri));
	        setAuthorImage(getAuthorImage(uri));
		}
		else {
			quotationObserver.setUri(uri);
			getContentResolver().registerContentObserver(uri, false, quotationObserver);

			setQuote("Waiting for quotation to load ...");
			setAuthor("");

			Bundle extras = new Bundle();
	        extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false);
	        extras.putBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, false);
	        extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
	        extras.putString(QuotationSyncAdapter.SYNC_EXTRAS_QUOTATION_UPDATE, uri.toString());
			ContentResolver.requestSync(new QuotationAccount(), QuotationContract.AUTHORITY, extras);
		}
    }

The quote widget works similarly. Now the user clicks on the quote widget it will send an intent containing the quote’s Uri to the quote activity. The activity provides further details such as the author’s name and image.

device-2013-03-22-220229 device-2013-03-22-220213

Freebase also provides an image service. The application fetches the author’s image rather than trying to store it. The fetch is done in the background using an AsyncTask.

public class QuoteActivity extends Activity {

        ...

	public class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
	    ImageView bmImage;

	    public DownloadImageTask(ImageView bmImage) {
	        this.bmImage = bmImage;
	    }

	    protected Bitmap doInBackground(String... urls) {
	        String urldisplay = urls[0];
	        Bitmap bitmap = null;
	        try {
	            InputStream in = new java.net.URL(urldisplay).openStream();
	            bitmap = BitmapFactory.decodeStream(in);
	        } catch (Exception e) {
	            bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher);
	        }
	        return bitmap;
	    }

	    protected void onPostExecute(Bitmap result) {
	        bmImage.setImageBitmap(result);
	    }
	}

        ...

	public void setAuthorImage(String image) {
		Log.d(TAG, String.format("setAuthorImage - image: %s", image));
		ImageView imageView = (ImageView) findViewById(R.id.image);
		Uri uri = Uri.parse("https://usercontent.googleapis.com/freebase/v1/image" + image + "?maxwidth=200&maxheight=200&pad=true");
		Log.d(TAG, String.format("setAuthorImage - uri: %s", uri));
		new DownloadImageTask(imageView).execute(uri.toString());
	}

       ....
}
Advertisements
  1. No comments yet.
  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: