Power your app with Content Provider

Power your app with Content Provider

A mobile application must work in every situations.
One of the main problematic to manage while developing is the offline mode.

On Android, one of the way to manage access to the data is the ContentProvider, and it can work offline, but its power doesn't stop there. indeed, it is in the center of the Android system by allowing data sharing across application.

After explaining the basics of the ContentProvider, we will study in more details how it works and how to create one for your application.

9949dfe0542bd0fd32676d63c97a625f?s=128

Mathieu Calba

October 24, 2013
Tweet

Transcript

  1. Power your app with Content Provider Droidcon London 10-24-2013

  2. @Mathieu_Calba http://bit.ly/11FFK9z http://bit.ly/Y7wFiU

  3. 1- Basics

  4. Definition Generic Interface to access data across multiple Android components

  5. None
  6. Android is built upon reusable components Activity, Service, Broadcast Receiver,

    Content Provider
  7. Android defines how we can communicate with them Content URIs

    & Content Resolver for Content Provider
  8. Content Authority A symbolic name to your provider (must be

    unique across every Android applications) tips : use your package name & your provider name sample : com.mathieucalba.yana.provider
  9. An URI that identify data in a provider Contains an

    authority and a path : content://content_authority/path... samples : content://com.mathieucalba.yana.provider/article content://com.mathieucalba.yana.provider/article/24 content://com.mathieucalba.yana.provider/article/24/image Content URI
  10. Content Types A String in MIME that describes the type

    of data returned by a provider for a given Content URI Contains an type and a subtype : type/subType samples : vnd.android.cursor.dir/vnd.mathieucalba.yana.article vnd.android.cursor.item/vnd.mathieucalba.yana.article
  11. ContentResolver Handles Content Provider selection based upon a Content URI

    CRUD methods (insert, query, update, delete) Batch methods (applyBatch, bulkInsert) Hides inter-process communication
  12. Some public ContentProviders Calendar (CalendarContract) Contacts (ContactContract) Media (MediaStore) Dictionary

    (UserDictionary) Any.DO (http://tech.any.do/content-provider-for-any-do/) ...
  13. 2- Using one

  14. query ContentResolver resolver = getContentResolver(); String[] projection = new String[]

    {UserDictionary.Words._ID, UserDictionary.Words.WORD}; String selectionClause = UserDictionary.Words.WORD + " = ?"; String[] selectionArgs = new String[] {"Android"}; String sortOrder = UserDictionary.Words.WORD + " ASC"; Cursor cursor = resolver.query(UserDictionary.Words.CONTENT_URI, projection, selectionClause, selectionArgs, sortOrder);
  15. insert ContentResolver resolver = getContentResolver(); ContentValues values = new ContentValues();

    values.put(UserDictionary.Words.WORD, "Droid"); Uri insertedWordUri = resolver.insert(UserDictionary.Words.CONTENT_URI, values);
  16. update ContentResolver resolver = getContentResolver(); ContentValues values = new ContentValues();

    values.put(UserDictionary.Words.WORD, "Droidcon2013"); String selectionClause = UserDictionary.Words.WORD + " = ?"; String[] selectionArgs = new String[] {"Droidcon"}; long nbUpdated = resolver.update(UserDictionary.Words.CONTENT_URI, values, selectionClause, selectionArgs);
  17. delete ContentResolver resolver = getContentResolver(); String selectionClause = UserDictionary.Words.WORD +

    " = ?"; String[] selectionArgs = new String[] {"Droidcon"}; long nbDeleted = resolver.delete(UserDictionary.Words.CONTENT_URI, selectionClause, selectionArgs);
  18. These methods block the UI thread You must use them

    on another thread (with an AsyncQueryHandler, a CursorLoader, a Thread & other classic methods)
  19. 3- Creating one

  20. Best Practice

  21. YANA (Yet Another News App) Sample application : https://github.com/MathieuCalba/android-ui-design-pattern

  22. 1- Contract public class YANAContract { public static String CONTENT_AUTHORITY

    = "com.mathieucalba.yana.provider"; protected static Uri BASE_CONTENT_URI = Uri.parse("content://" + CONTENT_AUTHORITY); protected static String VENDOR_NAME = "vnd.mathieucalba.yana."; protected static final String PATH_ARTICLE = "article"; public interface Tables { String ARTICLE = "article"; } public interface ArticleColumns { static final String TITLE = "title"; // Title (TEXT) static final String CONTENT = "content"; // Content (TEXT) static final String TIMESTAMP = "timestamp"; // Timestamp (INTEGER) } ...
  23. 1- Contract public class YANAContract { public static String CONTENT_AUTHORITY

    = "com.mathieucalba.yana.provider"; protected static Uri BASE_CONTENT_URI = Uri.parse("content://" + CONTENT_AUTHORITY); protected static String VENDOR_NAME = "vnd.mathieucalba.yana"; protected static final String PATH_ARTICLE = "article"; public interface Tables { String ARTICLE = "article"; } public interface ArticleColumns { static final String TITLE = "title"; // Title (TEXT) static final String CONTENT = "content"; // Content (TEXT) static final String TIMESTAMP = "timestamp"; // Timestamp (INTEGER) } ...
  24. 1- Contract ... public static class ArticleTable implements BaseColumns, ArticleColumns

    { public static final Uri CONTENT_URI = BASE_CONTENT_URI.buildUpon().appendPath(PATH_ARTICLE).build(); public static final String CONTENT_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE + VENDOR_NAME + PATH_ARTICLE; public static final String CONTENT_ITEM_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE + VENDOR_NAME + PATH_ARTICLE; public static final String DEFAULT_SORT = Tables.ARTICLE + "." + ArticleColumns.TIMESTAMP + " DESC"; /** Build URI for all articles */ public static Uri buildUri() { return CONTENT_URI; } /** Build URI for one article */ public static Uri buildUriWithArticleId(int id) { return CONTENT_URI.buildUpon().appendPath(String.valueOf(id)).build(); } public static String getId(Uri uri) { return uri.getPathSegments().get(1); } } }
  25. 1- Contract ... public static class ArticleTable implements BaseColumns, ArticleColumns

    { public static final Uri CONTENT_URI = BASE_CONTENT_URI.buildUpon().appendPath(PATH_ARTICLE).build(); public static final String CONTENT_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE + VENDOR_NAME + PATH_ARTICLE; public static final String CONTENT_ITEM_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE + VENDOR_NAME + PATH_ARTICLE; public static final String DEFAULT_SORT = Tables.ARTICLE + "." + ArticleColumns.TIMESTAMP + " DESC"; /** Build URI for all articles */ public static Uri buildUri() { return CONTENT_URI; } /** Build URI for one article */ public static Uri buildUriWithArticleId(int id) { return CONTENT_URI.buildUpon().appendPath(String.valueOf(id)).build(); } public static String getId(Uri uri) { return uri.getPathSegments().get(1); } } }
  26. 2- Data public class YANADatabase extends SQLiteOpenHelper { private static

    final String DATABASE_NAME = "yana_database"; private static final int DATABASE_VERSION = 1; public YANADatabase(Context context, CursorFactory factory, int version) { super(context, DATABASE_NAME, factory, version); } @Override public void onCreate(SQLiteDatabase db) { createDatabase(db); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { if (oldVersion == 0) { db.execSQL("DROP TABLE article"); createDatabase(db); } } ...
  27. 2- Data ... protected void createDatabase(SQLiteDatabase db) { final String

    articleTable = "CREATE TABLE " + YANAContract.Tables.ARTICLE // + " ('" // + BaseColumns._ID + "' INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, '" // + YANAContract.ArticleColumns.TITLE + "' TEXT, '" // + YANAContract.ArticleColumns.CONTENT + "' TEXT, '" // + YANAContract.ArticleColumns.TIMESTAMP + "' TIMESTAMP NOT NULL );"; db.execSQL(articleTable); } }
  28. 3- Provider public class YANAProvider extends ContentProvider { protected static

    UriMatcher sUriMatcher; private static final int ARTICLE = 100; private static final int ARTICLE_ID = 101; protected static UriMatcher buildUriMatcher(final String authority) { final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH); matcher.addURI(authority, YANAContract.PATH_ARTICLE + "/#", ARTICLE_ID); matcher.addURI(authority, YANAContract.PATH_ARTICLE, ARTICLE); return matcher; } protected YANADatabase mDatabase; @Override public boolean onCreate() { mDatabase = new YANADatabase(getContext(), null, YANADatabase.DATABASE_VERSION); sUriMatcher = buildUriMatcher(YANAContract.CONTENT_AUTHORITY); return true; } ...
  29. 3- Provider public class YANAProvider extends ContentProvider { protected static

    UriMatcher sUriMatcher; private static final int ARTICLE = 100; private static final int ARTICLE_ID = 101; protected static UriMatcher buildUriMatcher(final String authority) { final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH); matcher.addURI(authority, YANAContract.PATH_ARTICLE + "/#", ARTICLE_ID); matcher.addURI(authority, YANAContract.PATH_ARTICLE, ARTICLE); return matcher; } protected YANADatabase mDatabase; @Override public boolean onCreate() { mDatabase = new YANADatabase(getContext(), null, YANADatabase.DATABASE_VERSION); sUriMatcher = buildUriMatcher(YANAContract.CONTENT_AUTHORITY); return true; } ...
  30. 3- Provider ... @Override public String getType(Uri uri) { final

    int match = sUriMatcher.match(uri); switch (match) { case ARTICLE: return YANAContract.ArticleTable.CONTENT_TYPE; case ARTICLE_ID: return YANAContract.ArticleTable.CONTENT_ITEM_TYPE; } return null; } ...
  31. 3- Provider ... @Override public Cursor query(Uri uri, String[] projection,

    String selection, String[] selectionArgs, String sortOrder) { final SQLiteDatabase db = mDatabase.getReadableDatabase(); if (db == null || !db.isOpen()) { return null; } final int match = sUriMatcher.match(uri); switch (match) { case ARTICLE: { final Cursor c = db.query(YANAContract.Tables.ARTICLE, projection, selection, selectionArgs, null, null, sortOrder); c.setNotificationUri(getContext().getContentResolver(), uri); return c; } ...
  32. 3- Provider ... @Override public Cursor query(Uri uri, String[] projection,

    String selection, String[] selectionArgs, String sortOrder) { final SQLiteDatabase db = mDatabase.getReadableDatabase(); if (db == null || !db.isOpen()) { return null; } final int match = sUriMatcher.match(uri); switch (match) { case ARTICLE: { final Cursor c = db.query(YANAContract.Tables.ARTICLE, projection, selection, selectionArgs, null, null, sortOrder); c.setNotificationUri(getContext().getContentResolver(), uri); return c; } ...
  33. 3- Provider ... case ARTICLE_ID: { final StringBuilder select =

    new StringBuilder(); if (!TextUtils.isEmpty(selection)) { select.append(selection); select.append(" AND "); } select.append(YANAContract.Tables.ARTICLE); select.append('.'); select.append(YANAContract.ArticleTable._ID); select.append(" = "); select.append(YANAContract.ArticleTable.getId(uri)); selection = select.toString(); final Cursor c = db.query(YANAContract.Tables.ARTICLE, projection, selection, selectionArgs, null, null, sortOrder); c.setNotificationUri(getContext().getContentResolver(), uri); return c; } default: if (BuildConfig.DEBUG) { Log.w(TAG, "match (" + match + ") is not handled in query"); } return null; } } ...
  34. 3- Provider ... case ARTICLE_ID: { final StringBuilder select =

    new StringBuilder(); if (!TextUtils.isEmpty(selection)) { select.append(selection); select.append(" AND "); } select.append(YANAContract.Tables.ARTICLE); select.append('.'); select.append(YANAContract.ArticleTable._ID); select.append(" = "); select.append(YANAContract.ArticleTable.getId(uri)); selection = select.toString(); final Cursor c = db.query(YANAContract.Tables.ARTICLE, projection, selection, selectionArgs, null, null, sortOrder); c.setNotificationUri(getContext().getContentResolver(), uri); return c; } default: if (BuildConfig.DEBUG) { Log.w(TAG, "match (" + match + ") is not handled in query"); } return null; } } ...
  35. 3- Provider ... @Override public Uri insert(Uri uri, ContentValues values)

    { final SQLiteDatabase db = mDatabase.getWritableDatabase(); if (db == null || !db.isOpen()) { return null; } final int match = sUriMatcher.match(uri); switch (match) { case ARTICLE: case ARTICLE_ID: { db.insertOrThrow(YANAContract.Tables.ARTICLE, null, values); getContext().getContentResolver().notifyChange(uri, null); return YANAContract.ArticleTable.buildUriWithArticleId( values.getAsInteger(YANAContract.ArticleColumns.ID)); } default: if (BuildConfig.DEBUG) { Log.w(TAG, "match (" + match + ") is not handled in insert"); } return null; } } ...
  36. 3- Provider ... @Override public Uri insert(Uri uri, ContentValues values)

    { final SQLiteDatabase db = mDatabase.getWritableDatabase(); if (db == null || !db.isOpen()) { return null; } final int match = sUriMatcher.match(uri); switch (match) { case ARTICLE: case ARTICLE_ID: { db.insertOrThrow(YANAContract.Tables.ARTICLE, null, values); getContext().getContentResolver().notifyChange(uri, null); return YANAContract.ArticleTable.buildUriWithArticleId( values.getAsInteger(YANAContract.ArticleColumns.ID)); } default: if (BuildConfig.DEBUG) { Log.w(TAG, "match (" + match + ") is not handled in insert"); } return null; } } ...
  37. 3- Provider ... @Override public int update(Uri uri, ContentValues values,

    String select, String[] selectArgs) { final SQLiteDatabase db = mDatabase.getWritableDatabase(); if (db == null || !db.isOpen()) { return 0; } final int match = sUriMatcher.match(uri); switch (match) { case ARTICLE: { ... return retVal; } case ARTICLE_ID: { ... return retVal; } default: if (BuildConfig.DEBUG) { Log.w(TAG, "match (" + match + ") is not handled in update"); } return 0; } } ...
  38. 3- Provider ... final int match = sUriMatcher.match(uri); switch (match)

    { case ARTICLE: { final int retVal = db.update(YANAContract.Tables.ARTICLE, values, selection, selectionArgs); getContext().getContentResolver().notifyChange(uri, null); return retVal; } case ARTICLE_ID: { final StringBuilder select = new StringBuilder(); if (!TextUtils.isEmpty(selection)) { select.append(selection); select.append(" AND "); } select.append(YANAContract.Tables.ARTICLE); select.append('.'); select.append(YANAContract.ArticleTable._ID); select.append(" = "); select.append(YANAContract.ArticleTable.getId(uri)); selection = select.toString(); final int retVal = db.update(YANAContract.Tables.ARTICLE, values, selection, selectionArgs); getContext().getContentResolver().notifyChange(uri, null); return retVal; } ...
  39. 3- Provider ... final int match = sUriMatcher.match(uri); switch (match)

    { case ARTICLE: { final int retVal = db.update(YANAContract.Tables.ARTICLE, values, selection, selectionArgs); getContext().getContentResolver().notifyChange(uri, null); return retVal; } case ARTICLE_ID: { final StringBuilder select = new StringBuilder(); if (!TextUtils.isEmpty(selection)) { select.append(selection); select.append(" AND "); } select.append(YANAContract.Tables.ARTICLE); select.append('.'); select.append(YANAContract.ArticleTable._ID); select.append(" = "); select.append(YANAContract.ArticleTable.getId(uri)); selection = select.toString(); final int retVal = db.update(YANAContract.Tables.ARTICLE, values, selection, selectionArgs); getContext().getContentResolver().notifyChange(uri, null); return retVal; } ...
  40. 3- Provider ... @Override public int delete(Uri uri, String selection,

    String[] selectionArgs){ final SQLiteDatabase db = mDatabase.getWritableDatabase(); if (db == null || !db.isOpen()) { return 0; } final int match = sUriMatcher.match(uri); switch (match) { case ARTICLE: { ... return retVal; } case ARTICLE_ID: { ... return retVal; } default: if (BuildConfig.DEBUG) { Log.w(TAG, "match (" + match + ") is not handled in delete"); } return 0; } } ...
  41. 3- Provider ... final int match = sUriMatcher.match(uri); switch (match)

    { case ARTICLE: { final int retVal = db.delete(YANAContract.Tables.ARTICLE, selection, selectionArgs); getContext().getContentResolver().notifyChange(uri, null); return retVal; } case ARTICLE_ID: { final StringBuilder select = new StringBuilder(); if (!TextUtils.isEmpty(selection)) { select.append(selection); select.append(" AND "); } select.append(YANAContract.Tables.ARTICLE); select.append('.'); select.append(YANAContract.ArticleTable._ID); select.append(" = "); select.append(YANAContract.ArticleTable.getId(uri)); selection = select.toString(); final int retVal = db.delete(YANAContract.Tables.ARTICLE, selection, selectionArgs); getContext().getContentResolver().notifyChange(uri, null); return retVal; } ...
  42. 3- Provider ... final int match = sUriMatcher.match(uri); switch (match)

    { case ARTICLE: { final int retVal = db.delete(YANAContract.Tables.ARTICLE, selection, selectionArgs); getContext().getContentResolver().notifyChange(uri, null); return retVal; } case ARTICLE_ID: { final StringBuilder select = new StringBuilder(); if (!TextUtils.isEmpty(selection)) { select.append(selection); select.append(" AND "); } select.append(YANAContract.Tables.ARTICLE); select.append('.'); select.append(YANAContract.ArticleTable._ID); select.append(" = "); select.append(YANAContract.ArticleTable.getId(uri)); selection = select.toString(); final int retVal = db.delete(YANAContract.Tables.ARTICLE, selection, selectionArgs); getContext().getContentResolver().notifyChange(uri, null); return retVal; } ...
  43. 4- Manifest <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.mathieucalba.yana" android:installLocation="auto" android:versionCode="1" android:versionName="1.0" > ...

    <application android:name="com.mathieucalba.yana.UIDesignPatternApplication" > ... <provider android:name="com.mathieucalba.yana.provider.YANAProvider" android:authorities="com.mathieucalba.yana.provider" android:exported="false" /> ... </application> ... </manifest>
  44. 4- CursorLoader

  45. CursorLoader Manage content loading from ContentProvider on another thread

  46. 1- restartLoader public class MyFragment extends Fragment implements LoaderCallbacks<Cursor> {

    protected static final int LOADER_ID_BASE_FEED_LIST = 1306170719; @Override public void onStart() { super.onStart(); if (fragment.getActivity() != null && !fragment.isDetached()) { final LoaderManager loaderManager = getActivity().getSupportLoaderManager(); if (loaderManager != null) { loaderManager.restartLoader(LOADER_ID_BASE_FEED_LIST, null, this); } } } ...
  47. 2- onCreateLoader public class MyFragment extends Fragment implements LoaderCallbacks<Cursor> {

    ... @Override public Loader<Cursor> onCreateLoader(int id, Bundle b) { if (id == LOADER_ID_BASE_FEED_LIST) { Uri uri = YANAContract.ArticleTable.buildUri(); return new CursorLoader(getActivity(), uri, PROJ_LIST.COLS, null, null, YANAContract.ArticleTable.DEFAULT_SORT); } return null; } public static class PROJ { } @Override public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { } @Override public void onLoaderReset(Loader<Cursor> loader) { } ... }
  48. 3- Projection public class MyFragment extends Fragment implements LoaderCallbacks<Cursor> {

    ... @Override public Loader<Cursor> onCreateLoader(int id, Bundle b) {} public static class PROJ { public static int _ID = 0; public static int TITLE = 1; public static int CONTENT = 2; public static String[] COLS = new String[] { // ArticleColumns._ID, // Tables.ARTICLE + "." + ArticleColumns.TITLE, // Tables.ARTICLE + "." + ArticleColumns.CONTENT // }; } @Override public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {} @Override public void onLoaderReset(Loader<Cursor> loader) {} ... }
  49. 4- onLoadFinished public class MyFragment extends Fragment implements LoaderCallbacks<Cursor> {

    ... @Override public Loader<Cursor> onCreateLoader(int id, Bundle b) { } public static class PROJ { } @Override public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { final int id = loader.getId(); if (id == LOADER_ID_BASE_FEED_LIST) { mFeedListAdapter.swapCursor(cursor); } } @Override public void onLoaderReset(Loader<Cursor> loader) { } ... }
  50. 5- onLoaderReset public class MyFragment extends Fragment implements LoaderCallbacks<Cursor> {

    ... @Override public Loader<Cursor> onCreateLoader(int id, Bundle b) { } public static class PROJ { } @Override public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { } @Override public void onLoaderReset(Loader<Cursor> loader) { final int id = loader.getId(); if (id == LOADER_ID_BASE_FEED_LIST) { mFeedListAdapter.swapCursor(null); } } ... }
  51. Be careful with the ids ! Must be unique in

    one LoaderManager instance !
  52. 5- AsyncQueryHandler

  53. AsyncQueryHandler Manage ContentProvider’s Operation (query, insert, update, delete) on another

    thread
  54. AsyncQueryHandler public class ArticleAsyncTaskLoader extends AsyncQueryHandler { protected static final

    int TOKEN_STAR_ARTICLE = 1304141728; public interface StarArticleListener { void onStarArticleSuccessfull(String articleId, boolean vote); void onStarArticleFailed(String articleId, boolean vote, String error); } public ArticleAsyncTaskLoader(ContentResolver cr) { super(cr); } public void starArticle(String articleId, boolean vote, StarArticleListener listener) { final ContentValues values = new ContentValues(); values.put(YanaContract.Articles.IS_FAVORITE, vote ? 1 : 0); final StarArticleCookie cookie = new StarArticleCookie(listener, articleId, vote); final Uri uri = YanaContract.Articles.buildArticleUri(articleId); startUpdate(TOKEN_STAR_ARTICLE, cookie, uri, values, null, null); } ...
  55. AsyncQueryHandler public class ArticleAsyncTaskLoader extends AsyncQueryHandler { protected static final

    int TOKEN_STAR_ARTICLE = 1304141728; public interface StarArticleListener { void onStarArticleSuccessfull(String articleId, boolean vote); void onStarArticleFailed(String articleId, boolean vote, String error); } public ArticleAsyncTaskLoader(ContentResolver cr) { super(cr); } public void starArticle(String articleId, boolean vote, StarArticleListener listener) { final ContentValues values = new ContentValues(); values.put(YanaContract.Articles.IS_FAVORITE, vote ? 1 : 0); final StarArticleCookie cookie = new StarArticleCookie(listener, articleId, vote); final Uri uri = YanaContract.Articles.buildArticleUri(articleId); startUpdate(TOKEN_STAR_ARTICLE, cookie, uri, values, null, null); } ...
  56. AsyncQueryHandler ... protected static class StarArticleCookie { final WeakReference<ArticleAsyncTaskLoader.StarArticleListener> mRefListener;

    final String mArticleId; final boolean mVote; public StarArticleCookie(ArticleAsyncTaskLoader.StarArticleListener listener, String articleId, boolean vote) { super(); mRefListener = new WeakReference<ArticleAsyncTaskLoader.StarArticleListener>(listener); mArticleId = articleId; mVote = vote; } } ...
  57. AsyncQueryHandler ... @Override protected void onUpdateComplete(int token, Object cookie, int

    result) { super.onUpdateComplete(token, cookie, result); switch (token) { case TOKEN_STAR_ARTICLE: if (!(cookie instanceof StarArticleCookie)) { return; } final StarArticleCookie cookieStarArticle = (StarArticleCookie) cookie; if (result == 0) { final WeakReference<StarArticleListener> refListener = cookieStarArticle.mRefListener; if (refListener != null) { final StarArticleListener listener = refListener.get(); if (listener != null) { listener.onStarArticleFailed(cookieStarArticle.mArticleId, cookieStarArticle.mVote, ""); } } return; } ... } } }
  58. AsyncQueryHandler ... @Override protected void onUpdateComplete(int token, Object cookie, int

    result) { super.onUpdateComplete(token, cookie, result); switch (token) { case TOKEN_STAR_ARTICLE: if (!(cookie instanceof StarArticleCookie)) { return; } final StarArticleCookie cookieStarArticle = (StarArticleCookie) cookie; if (result == 0) { final WeakReference<StarArticleListener> refListener = cookieStarArticle.mRefListener; if (refListener != null) { final StarArticleListener listener = refListener.get(); if (listener != null) { listener.onStarArticleFailed(cookieStarArticle.mArticleId, cookieStarArticle.mVote, ""); } } return; } ... } } }
  59. AsyncQueryHandler ... @Override protected void onUpdateComplete(int token, Object cookie, int

    result) { super.onUpdateComplete(token, cookie, result); switch (token) { case TOKEN_STAR_ARTICLE: ... if (result == 0) { ... return; } final WeakReference<StarArticleListener> refListener = cookieStarArticle.mRefListener; if (refListener != null) { final StarArticleListener listener = refListener.get(); if (listener != null) { listener.onStarArticleSuccessfull(cookieStarArticle.mArticleId, cookieStarArticle.mVote); } } break; default: break; } } }
  60. AsyncQueryHandler public class MyFragment extends Fragment implements ArticleAsyncTaskLoader.StarArticleListener{ public starArticle(int

    articleId, boolean addFavorite) { final ContentResolver cr = getActivity().getContentResolver(); new ArticleAsyncTaskLoader(cr).starArticle(articleId, addFavorite, this); } @Override public void onStarArticleSuccessfull(String articleId, boolean vote) { // inform user } @Override public void onStarArticleFailed(String articleId, boolean vote, String error) { // inform user } }
  61. 6- More to explore

  62. Batching requests ContentProviderOperation is an helper class to create Query,

    Insert, Update & Delete requests to a ContentProvider : ContentProviderOperation.newAssertQuery(Uri uri); ContentProviderOperation.newInsert(Uri uri); ContentProviderOperation.newUpdate(Uri uri); ContentProviderOperation.newDelete(Uri uri); Used with ContentResolver’s apply batch method : ContentProviderResult[] applyBatch(String authority, ArrayList<ContentProviderOperation> operations) throws RemoteException, OperationApplicationException;
  63. Attach Database (Multiple database used like only one : SQL

    keyword : ATTACH) ProviderTestCase2 (Unit test your provider) Using data URI on your activities to open activities based on Content URIs
  64. Thanks

  65. Questions ? @Mathieu_Calba