Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

1- Basics

Slide 4

Slide 4 text

Definition Generic Interface to access data across multiple Android components

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

ContentResolver Handles Content Provider selection based upon a Content URI CRUD methods (insert, query, update, delete) Batch methods (applyBatch, bulkInsert) Hides inter-process communication

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

2- Using one

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

delete ContentResolver resolver = getContentResolver(); String selectionClause = UserDictionary.Words.WORD + " = ?"; String[] selectionArgs = new String[] {"Droidcon"}; long nbDeleted = resolver.delete(UserDictionary.Words.CONTENT_URI, selectionClause, selectionArgs);

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

3- Creating one

Slide 20

Slide 20 text

Best Practice

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

4- Manifest ... ... ... ...

Slide 44

Slide 44 text

4- CursorLoader

Slide 45

Slide 45 text

CursorLoader Manage content loading from ContentProvider on another thread

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

5- AsyncQueryHandler

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

6- More to explore

Slide 62

Slide 62 text

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;

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

Thanks

Slide 65

Slide 65 text

Questions ? @Mathieu_Calba