Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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.

Mathieu Calba

October 24, 2013
Tweet

More Decks by Mathieu Calba

Other Decks in Programming

Transcript

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

    View Slide

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

    View Slide

  3. 1- Basics

    View Slide

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

    View Slide

  5. View Slide

  6. Android is built upon
    reusable components
    Activity, Service, Broadcast Receiver,
    Content Provider

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  12. Some public ContentProviders
    Calendar (CalendarContract)
    Contacts (ContactContract)
    Media (MediaStore)
    Dictionary (UserDictionary)
    Any.DO (http://tech.any.do/content-provider-for-any-do/)
    ...

    View Slide

  13. 2- Using one

    View Slide

  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);

    View Slide

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

    View Slide

  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);

    View Slide

  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);

    View Slide

  18. These methods block the UI thread
    You must use them on another thread
    (with an AsyncQueryHandler, a CursorLoader,
    a Thread & other classic methods)

    View Slide

  19. 3- Creating one

    View Slide

  20. Best Practice

    View Slide

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

    View Slide

  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)
    }
    ...

    View Slide

  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)
    }
    ...

    View Slide

  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);
    }
    }
    }

    View Slide

  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);
    }
    }
    }

    View Slide

  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);
    }
    }
    ...

    View Slide

  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);
    }
    }

    View Slide

  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;
    }
    ...

    View Slide

  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;
    }
    ...

    View Slide

  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;
    }
    ...

    View Slide

  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;
    }
    ...

    View Slide

  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;
    }
    ...

    View Slide

  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;
    }
    }
    ...

    View Slide

  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;
    }
    }
    ...

    View Slide

  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;
    }
    }
    ...

    View Slide

  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;
    }
    }
    ...

    View Slide

  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;
    }
    }
    ...

    View Slide

  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;
    }
    ...

    View Slide

  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;
    }
    ...

    View Slide

  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;
    }
    }
    ...

    View Slide

  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;
    }
    ...

    View Slide

  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;
    }
    ...

    View Slide

  43. 4- Manifest
    package="com.mathieucalba.yana"
    android:installLocation="auto"
    android:versionCode="1"
    android:versionName="1.0" >
    ...
    android:name="com.mathieucalba.yana.UIDesignPatternApplication" >
    ...
    android:name="com.mathieucalba.yana.provider.YANAProvider"
    android:authorities="com.mathieucalba.yana.provider"
    android:exported="false" />
    ...

    ...

    View Slide

  44. 4- CursorLoader

    View Slide

  45. CursorLoader
    Manage
    content loading
    from ContentProvider
    on another thread

    View Slide

  46. 1- restartLoader
    public class MyFragment extends Fragment implements LoaderCallbacks {
    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);
    }
    }
    }
    ...

    View Slide

  47. 2- onCreateLoader
    public class MyFragment extends Fragment
    implements LoaderCallbacks {
    ...
    @Override
    public Loader 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 loader, Cursor cursor) {
    }
    @Override
    public void onLoaderReset(Loader loader) {
    }
    ...
    }

    View Slide

  48. 3- Projection
    public class MyFragment extends Fragment
    implements LoaderCallbacks {
    ...
    @Override
    public Loader 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 loader, Cursor cursor) {}
    @Override
    public void onLoaderReset(Loader loader) {}
    ...
    }

    View Slide

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

    View Slide

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

    View Slide

  51. Be careful with the ids !
    Must be unique
    in one LoaderManager instance !

    View Slide

  52. 5- AsyncQueryHandler

    View Slide

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

    View Slide

  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);
    }
    ...

    View Slide

  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);
    }
    ...

    View Slide

  56. AsyncQueryHandler
    ...
    protected static class StarArticleCookie {
    final WeakReference mRefListener;
    final String mArticleId;
    final boolean mVote;
    public StarArticleCookie(ArticleAsyncTaskLoader.StarArticleListener listener,
    String articleId, boolean vote) {
    super();
    mRefListener = new WeakReference(listener);
    mArticleId = articleId;
    mVote = vote;
    }
    }
    ...

    View Slide

  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 refListener = cookieStarArticle.mRefListener;
    if (refListener != null) {
    final StarArticleListener listener = refListener.get();
    if (listener != null) {
    listener.onStarArticleFailed(cookieStarArticle.mArticleId, cookieStarArticle.mVote, "");
    }
    }
    return;
    }
    ...
    }
    }
    }

    View Slide

  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 refListener = cookieStarArticle.mRefListener;
    if (refListener != null) {
    final StarArticleListener listener = refListener.get();
    if (listener != null) {
    listener.onStarArticleFailed(cookieStarArticle.mArticleId, cookieStarArticle.mVote, "");
    }
    }
    return;
    }
    ...
    }
    }
    }

    View Slide

  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 refListener = cookieStarArticle.mRefListener;
    if (refListener != null) {
    final StarArticleListener listener = refListener.get();
    if (listener != null) {
    listener.onStarArticleSuccessfull(cookieStarArticle.mArticleId, cookieStarArticle.mVote);
    }
    }
    break;
    default:
    break;
    }
    }
    }

    View Slide

  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
    }
    }

    View Slide

  61. 6- More to explore

    View Slide

  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 operations)
    throws RemoteException, OperationApplicationException;

    View Slide

  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

    View Slide

  64. Thanks

    View Slide

  65. Questions ?
    @Mathieu_Calba

    View Slide