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 full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  6. 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 full-size slide

  7. 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 full-size slide

  8. 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 full-size slide

  9. 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 full-size slide

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

    View full-size slide

  11. 2- Using one

    View full-size slide

  12. 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 full-size slide

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

    View full-size slide

  14. 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 full-size slide

  15. 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 full-size slide

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

    View full-size slide

  17. 3- Creating one

    View full-size slide

  18. Best Practice

    View full-size slide

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

    View full-size slide

  20. 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 full-size slide

  21. 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 full-size slide

  22. 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 full-size slide

  23. 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 full-size slide

  24. 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 full-size slide

  25. 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 full-size slide

  26. 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 full-size slide

  27. 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 full-size slide

  28. 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 full-size slide

  29. 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 full-size slide

  30. 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 full-size slide

  31. 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 full-size slide

  32. 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 full-size slide

  33. 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 full-size slide

  34. 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 full-size slide

  35. 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 full-size slide

  36. 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 full-size slide

  37. 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 full-size slide

  38. 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 full-size slide

  39. 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 full-size slide

  40. 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 full-size slide

  41. 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 full-size slide

  42. 4- CursorLoader

    View full-size slide

  43. CursorLoader
    Manage
    content loading
    from ContentProvider
    on another thread

    View full-size slide

  44. 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 full-size slide

  45. 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 full-size slide

  46. 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 full-size slide

  47. 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 full-size slide

  48. 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 full-size slide

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

    View full-size slide

  50. 5- AsyncQueryHandler

    View full-size slide

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

    View full-size slide

  52. 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 full-size slide

  53. 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 full-size slide

  54. 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 full-size slide

  55. 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 full-size slide

  56. 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 full-size 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 (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 full-size slide

  58. 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 full-size slide

  59. 6- More to explore

    View full-size slide

  60. 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 full-size slide

  61. 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 full-size slide

  62. Questions ?
    @Mathieu_Calba

    View full-size slide