android loaders : reloaded
DESCRIPTION
All you ever wanted to know about Android Loaders and never dared to ask. Important: I no longer recommend to use a Loader for "one-shot" actions because it's complicated and has a few side-effects. So I recommend to still use AsyncTasks in that case. You can create an AsyncTask inside a Fragment with setRetainInstance(true) to keep the same AsyncTask instance accross configuration changes, but beware not to update the view or interact with the Activity if the result arrives while the fragment is stopped. If you don't need the result, a static AsyncTask will do the job.TRANSCRIPT
![Page 1: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/1.jpg)
Android LoadersR E L O A D E D
Christophe Beyls
Brussels GTUG13th march 2013 READY.
LOAD "*",8,1
SEARCHING FOR *LOADINGREADY.RUN▀
![Page 2: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/2.jpg)
About the speaker
● Developer living in Brussels.● Likes coding, hacking devices,
travelling, movies, music, (LOL)cats.● Programs mainly in Java and C#.● Uses the Android SDK nearly every
day at work.
@BladeCoder
![Page 3: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/3.jpg)
About the speaker
![Page 4: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/4.jpg)
(Big) Agenda
● A bit of History: from Threads to Loaders● Introduction to Loaders● Using the LoaderManager● Avoiding common mistakes● Implementing a basic Loader● More Loader examples● Databases and CursorLoaders● Overcoming Loaders limitations
![Page 5: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/5.jpg)
A bit of History
1. Plain Threadsfinal Handler handler = new Handler(new Handler.Callback() {
@Overridepublic boolean handleMessage(Message msg) {
switch(msg.what) {case RESULT_WHAT:
handleResult((Result) msg.obj);return true;
}return false;
}});
Thread thread = new Thread(new Runnable() {
@Overridepublic void run() {
Result result = doStuff();if (isResumed()) {
handler.sendMessage(handler.obtainMessage(RESULT_WHAT, result));}
}});thread.start();
![Page 6: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/6.jpg)
A bit of History
1. Plain ThreadsDifficulties:● Requires you to post the result back on the
main thread;● Cancellation must be handled manually;● Want a thread pool?
You need to implement it yourself.
![Page 7: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/7.jpg)
A bit of History
2. AsyncTask (Android's SwingWorker)● Handles thread switching for you : result is
posted to the main thread.● Manages scheduling for you.● Handles cancellation: if you call cancel(),
onPostExecute() will not be called.● Allows to report progress.
![Page 8: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/8.jpg)
A bit of History
2. AsyncTaskprivate class DownloadFilesTask extends AsyncTask<Void, Integer, Result> {
@Overrideprotected void onPreExecute() {
// Something like showing a progress bar}
@Overrideprotected Result doInBackground(Void... params) {
Result result = new Result();for (int i = 0; i < STEPS; i++) {
result.add(doStuff());publishProgress(100 * i / STEPS);
}return result;
}
@Overrideprotected void onProgressUpdate(Integer... progress) {
setProgressPercent(progress[0]);}
@Overrideprotected void onPostExecute(Result result) {
handleResult(result);}
}
![Page 9: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/9.jpg)
A bit of History2. AsyncTaskProblems:● You need to keep a reference to each running
AsyncTask to be able to cancel it when your Activity is destroyed.
● Memory leaks: as long as the AsyncTask runs, it keeps a reference to its enclosing Activity even if the Activity has already been destroyed.
● Results arriving after the Activity has been recreated (orientation change) are lost.
![Page 10: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/10.jpg)
A bit of History
2. AsyncTaskA less known but big problem.
Demo
![Page 11: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/11.jpg)
A bit of History2. AsyncTaskAsyncTask scheduling varies between Android versions:● Before 1.6, they run in sequence on a single thread.● From 1.6 to 2.3, they run in parallel on a thread pool.● Since 3.0, back to the old behaviour by default! They
run in sequence, unless you execute them with executeOnExecutor() with a ThreadPoolExecutor.
→ No parallelization by default on modern phones.
![Page 12: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/12.jpg)
A bit of History2. AsyncTaskA workaround:
1. public class ConcurrentAsyncTask {2. public static void execute(AsyncTask as) {3. if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {4. as.execute();5. } else {6. as.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);7. }8. }9. }
... but you should really use Loaders instead.
![Page 13: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/13.jpg)
Loaders to the rescue● Allows an Activity or Fragment to reconnect to the
same Loader after recreation and retrieve the last result.
● If the result comes after a Loader has been disconnected from an Activity/Fragment, it can keep it in cache to deliver it when reconnected to the recreated Activity/Fragment.
● A Loader monitors its data source and delivers new results when the content changes.
● Loaders handle allocation/disallocation of resources associated with the result (example: Cursors).
![Page 14: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/14.jpg)
Loaders to the rescueIf you need to perform any kind of asynchronous load in an Activity or Fragment, you must never use AsyncTask again.
And don't do like this man because Loaders are much more than just CursorLoaders.
![Page 15: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/15.jpg)
Using the LoaderManager● Simple API to allow Activities and Fragments to
interact with Loaders.● One instance of LoaderManager for each Activity
and each Fragment. They don't share Loaders.● Main methods:
○ initLoader(int id, Bundle args, LoaderCallbacks<D> callbacks)
○ restartLoader(int id, Bundle args, LoaderCallbacks<D> callbacks)
○ destroyLoader(int id)○ getLoader(int id)
![Page 16: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/16.jpg)
Using the LoaderManagerprivate final LoaderCallbacks<Result> loaderCallbacks = new LoaderCallbacks<Result>() {
@Overridepublic Loader<Result> onCreateLoader(int id, Bundle args) {
return new MyLoader(getActivity(), args.getLong("id"));}
@Overridepublic void onLoadFinished(Loader<Result> loader, Result result) {
handleResult(result);}
@Overridepublic void onLoaderReset(Loader<Result> loader) {}
};
Never call a standard Loader method yourself directly on the Loader. Always use the LoaderManager.
![Page 17: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/17.jpg)
Using the LoaderManagerWhen to init Loaders at Activity/Fragment startupActivities
@Overrideprotected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);...getSupportLoaderManager().initLoader(LOADER_ID, null, callbacks);
}
Fragments
@Overridepublic void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);...getLoaderManager().initLoader(LOADER_ID, null, callbacks);
}
![Page 18: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/18.jpg)
Loaders lifecycle
A loader has 3 states:● Started● Stopped● Reset
The LoaderManager automatically changes the state of the Loaders according to the Activity or Fragment state.
![Page 19: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/19.jpg)
Loaders lifecycle● Activity/Fragment starts
→ Loader starts: onStartLoading()● Activity becomes invisible or Fragment is detached
→ Loader stops: onStopLoading()● Activity/Fragment is recreated → no callback.
The LoaderManager will continue to receive the results and keep them in a local cache.
● Activity/Fragment is destroyedor restartLoader() is calledor destroyLoader() is called→ Loader resets: onReset()
![Page 20: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/20.jpg)
Passing argumentsUsing args Bundle
private void onNewQuery(String query) {Bundle args = new Bundle();args.putString("query", query);getLoaderManager().restartLoader(LOADER_ID, args,
loaderCallbacks);}
@Overridepublic Loader<Result> onCreateLoader(int id, Bundle args) {
return new QueryLoader(getActivity(), args.getString("query"));}
![Page 21: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/21.jpg)
Passing argumentsUsing args Bundle● You don't need to use the args param most of the time.
Pass null.● The loaderCallBacks is part of your Fragment/Activity.
You can access your Fragment/Activity instance variables too.@Overridepublic void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);this.newsId = getArguments().getLong("newsId");
}
...@Overridepublic Loader<News> onCreateLoader(int id, Bundle args) {
return new NewsLoader(getActivity(), NewsFragment.this.newsId);}
![Page 22: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/22.jpg)
A LoaderManager bugWhen a Fragment is recreated on configuration change, its LoaderManager calls the onLoadFinished() callback twice to send back the last result of the Loader when you call initLoader() in onActivityCreated().
3 possible workarounds:1. Don't do anything. If your code permits it.2. Save the previous result and check if it's different.3. Call setRetainInstance(true) in onCreate().
![Page 23: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/23.jpg)
One-shot Loaders
Sometimes you only want to perform a loader action once.
Example: submitting a form.
● You call initLoader() in response to an action.● You need to reconnect to the loader on
orientation change to get the result.
![Page 24: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/24.jpg)
One-shot LoadersIn your LoaderCallbacks
@Overridepublic void onLoadFinished(Loader<Integer> loader, Result result) {
getLoaderManager().destroyLoader(LOADER_ID);... // Process the result
}
On Activity/Fragment creation@Overridepublic void onActivityCreated(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);...// Reconnect to the loader only if presentif (getLoaderManager().getLoader(LOADER_ID) != null) {
getLoaderManager().initLoader(LOADER_ID, null, this);}
}
![Page 25: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/25.jpg)
Common mistakes
1. Don't go crazy with loader idsYou don't need to:● Increment the loader id or choose a random loader id
each time you initialize or restart a Loader.This will prevent your Loaders from being reused and will create a complete mess!
Use a single unique id for each kind of Loader.
![Page 26: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/26.jpg)
Common mistakes
1. Don't go crazy with loader idsYou don't need to:● Create a loader id constant for each and
every kind of Loader accross your entire app.Each LoaderManager is independent.Just create private constants in your Activity or Fragment for each kind of loader in it.
![Page 27: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/27.jpg)
Common mistakes
2. Avoid FragmentManager ExceptionsYou can not create a FragmentTransaction directly in LoaderCallbacks.This includes any dialog you create as a DialogFragment.
Solution: Use a Handler to dispatch the FragmentTransaction.
![Page 28: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/28.jpg)
Common mistakes
2. Avoid FragmentManager Exceptionspublic class LinesFragment extends ContextMenuSherlockListFragment implements LoaderCallbacks<List<LineInfo>>, Callback {
...
@Overridepublic void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);handler = new Handler(this);adapter = new LinesAdapter(getActivity());
}
@Overridepublic void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setListAdapter(adapter);setListShown(false);
getLoaderManager().initLoader(LINES_LOADER_ID, null, this);}
![Page 29: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/29.jpg)
Common mistakes
2. Avoid FragmentManager Exceptions@Overridepublic Loader<List<LineInfo>> onCreateLoader(int id, Bundle args) {
return new LinesLoader(getActivity());}
@Overridepublic void onLoadFinished(Loader<List<LineInfo>> loader, List<LineInfo> data) {
if (data != null) {adapter.setLinesList(data);
} else if (isResumed()) {handler.sendEmptyMessage(LINES_LOADING_ERROR_WHAT);
}
// The list should now be shown.if (isResumed()) {
setListShown(true);} else {
setListShownNoAnimation(true);}
}
![Page 30: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/30.jpg)
Common mistakes
2. Avoid FragmentManager Exceptions@Overridepublic void onLoaderReset(Loader<List<LineInfo>> loader) {
adapter.setLinesList(null);}
@Overridepublic boolean handleMessage(Message message) {
switch (message.what) {case LINES_LOADING_ERROR_WHAT:
MessageDialogFragment .newInstance(R.string.error_title, R.string.lines_loading_error) .show(getFragmentManager());
return true;}return false;
}
![Page 31: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/31.jpg)
Implementing a basic Loader
3 classes provided by the support library:● Loader
Base abstract class.● AsyncTaskLoader
Abstract class, extends Loader.● CursorLoader
Extends AsyncTaskLoader.Particular implementation dedicated to querying ContentProviders.
![Page 32: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/32.jpg)
AsyncTaskLoader
Does it suffer from AsyncTask's limitations?
![Page 33: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/33.jpg)
AsyncTaskLoader
Does it suffer from AsyncTask's limitations?No, because it uses ModernAsyncTask internally, which has the same implementation on each Android version.private static final int CORE_POOL_SIZE = 5;private static final int MAXIMUM_POOL_SIZE = 128;private static final int KEEP_ALIVE = 1;
public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
private static volatile Executor sDefaultExecutor = THREAD_POOL_EXECUTOR;
![Page 34: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/34.jpg)
Implementing a basic Loader
IMPORTANT: Avoid memory leaksBy design, Loaders only keep a reference to the Application context so there is no leak:/** * Stores away the application context associated with context. Since Loaders can be * used across multiple activities it's dangerous to store the context directly. * * @param context used to retrieve the application context. */public Loader(Context context) { mContext = context.getApplicationContext();}
But each of your Loader inner classes must be declared static or they will keep an implicit reference to their parent!
![Page 35: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/35.jpg)
Implementing a basic Loader
We need to extend AsyncTaskLoader and implement its behavior.
![Page 36: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/36.jpg)
Implementing a basic Loader
The callbacks to implementMandatory● onStartLoading()● onStopLoading()● onReset()● onForceLoad() from Loader OR
loadInBackground() from AsyncTaskLoader
Optional● deliverResult() [override]
![Page 37: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/37.jpg)
Implementing a basic Loaderpublic abstract class BasicLoader<T> extends AsyncTaskLoader<T> {
public BasicLoader(Context context) {super(context);
}
@Overrideprotected void onStartLoading() {
forceLoad(); // Launch the background task}
@Overrideprotected void onStopLoading() {
cancelLoad(); // Attempt to cancel the current load task if possible}
@Overrideprotected void onReset() {
super.onReset();onStopLoading();
}}
![Page 38: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/38.jpg)
Implementing a basic Loaderpublic abstract class LocalCacheLoader<T> extends AsyncTaskLoader<T> {
private T mResult;
public AbstractAsyncTaskLoader(Context context) {super(context);
}
@Overrideprotected void onStartLoading() {
if (mResult != null) {// If we currently have a result available, deliver it// immediately.deliverResult(mResult);
}
if (takeContentChanged() || mResult == null) {// If the data has changed since the last time it was loaded// or is not currently available, start a load.forceLoad();
}}
...
![Page 39: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/39.jpg)
Implementing a basic Loader@Overrideprotected void onStopLoading() {
// Attempt to cancel the current load task if possible.cancelLoad();
}
@Overrideprotected void onReset() {
super.onReset();
onStopLoading();mResult = null;
}
@Overridepublic void deliverResult(T data) {
mResult = data;
if (isStarted()) {// If the Loader is currently started, we can immediately// deliver its results.super.deliverResult(data);
}}
}
![Page 40: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/40.jpg)
Implementing a basic Loader
What about a global cache instead?
public abstract class GlobalCacheLoader<T> extends AsyncTaskLoader<T> {...
@Overrideprotected void onStartLoading() {
T cachedResult = getCachedResult();if (cachedResult != null) {
// If we currently have a result available, deliver it// immediately.deliverResult(cachedResult);
}
if (takeContentChanged() || cachedResult == null) {// If the data has changed since the last time it was loaded// or is not currently available, start a load.forceLoad();
}}
...protected abstract T getCachedResult();
}
![Page 41: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/41.jpg)
Monitoring dataTwo Loader methods to help● onContentChanged()
If the Loader is started: will call forceLoad().If the Loader is stopped: will set a flag.
● takeContentChanged()Returns the flag value and clears the flag. @Overrideprotected void onStartLoading() {
if (mResult != null) {deliverResult(mResult);
}
if (takeContentChanged() || mResult == null) {forceLoad();
}}
![Page 42: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/42.jpg)
AutoRefreshLoaderpublic abstract class AutoRefreshLoader<T> extends LocalCacheLoader<T> {
private long interval;private Handler handler;private final Runnable timeoutRunnable = new Runnable() {
@Overridepublic void run() {
onContentChanged();}
};
public AutoRefreshLoader(Context context, long interval) {super(context);this.interval = interval;this.handler = new Handler();
}
...
![Page 43: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/43.jpg)
AutoRefreshLoader...
@Overrideprotected void onForceLoad() {
super.onForceLoad();handler.removeCallbacks(timeoutRunnable);handler.postDelayed(timeoutRunnable, interval);
}
@Overridepublic void onCanceled(T data) {
super.onCanceled(data);// Retry a refresh the next time the loader is startedonContentChanged();
}
@Overrideprotected void onReset() {
super.onReset();handler.removeCallbacks(timeoutRunnable);
}}
![Page 44: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/44.jpg)
CursorLoaderCursorLoader is a Loader dedicated to querying ContentProviders● It returns a database Cursor as result.● It performs the database query on a background
thread (it inherits from AsyncTaskLoader).● It replaces Activity.startManagingCursor(Cursor c)
It manages the Cursor lifecycle according to the Activity Lifecycle. → Never call close()
● It monitors the database and returns a new cursor when data has changed. → Never call requery()
![Page 45: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/45.jpg)
CursorLoaderUsage with a CursorAdapter in a ListFragment
@Overridepublic Loader<Cursor> onCreateLoader(int id, Bundle args) {
return new BookmarksLoader(getActivity(),args.getDouble("latitude"), args.getDouble("longitude"));
}
@Overridepublic void onLoadFinished(Loader<Cursor> loader, Cursor data) {
adapter.swapCursor(data);
// The list should now be shown.if (isResumed()) {
setListShown(true);} else {
setListShownNoAnimation(true);}
}
@Overridepublic void onLoaderReset(Loader<Cursor> loader) {
adapter.swapCursor(null);}
![Page 46: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/46.jpg)
CursorLoader
![Page 47: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/47.jpg)
SimpleCursorLoaderIf you don't need the complexity of a ContentProvider... but want to access a local database anyway
● SimpleCursorLoader is an abstract class based on CursorLoader with all the ContentProvider-specific stuff removed.
● You just need to override one method which performs the actual database query.
![Page 48: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/48.jpg)
SimpleCursorLoaderExample usage - bookmarksprivate static class BookmarksLoader extends SimpleCursorLoader {
private double latitude;private double longitude;
public BookmarksLoader(Context context, double latitude, double longitude) {super(context);this.latitude = latitude;this.longitude = longitude;
}
@Overrideprotected Cursor getCursor() {
return DatabaseManager.getInstance().getBookmarks(latitude, longitude);}
}
![Page 49: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/49.jpg)
SimpleCursorLoaderExample usage - bookmarkspublic class DatabaseManager {
private static final Uri URI_BOOKMARKS =
Uri.parse("sqlite://your.package.name/bookmarks");
...
public Cursor getBookmarks(double latitude, double longitude) {// A big database query you don't want to see...cursor.setNotificationUri(context.getContentResolver(), URI_BOOKMARKS);return cursor;
}
...
![Page 50: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/50.jpg)
SimpleCursorLoaderExample usage - bookmarks...
public boolean addBookmark(Bookmark bookmark) {
SQLiteDatabase db = helper.getWritableDatabase();db.beginTransaction();try {
// Other database stuff you don't want to see...long result = db.insert(DatabaseHelper.BOOKMARKS_TABLE_NAME, null,
values);
db.setTransactionSuccessful();// Will return -1 if the bookmark was already presentreturn result != -1L;
} finally {db.endTransaction();context.getContentResolver().notifyChange(URI_BOOKMARKS, null);
}}
}
![Page 51: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/51.jpg)
Loaders limitations
![Page 52: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/52.jpg)
Loaders limitations1. No built-in progress updates supportWorkaround: use LocalBroadcastManager.In the Activity:@Overrideprotected void onStart() {
// Receive loading status broadcasts in order to update the progress barLocalBroadcastManager.getInstance(this).registerReceiver(loadingStatusReceiver,
new IntentFilter(MyLoader.LOADING_ACTION));super.onStart();
}
@Overrideprotected void onStop() {
super.onStop();LocalBroadcastManager.getInstance(this)
.unregisterReceiver(loadingStatusReceiver);}
![Page 53: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/53.jpg)
Loaders limitations1. No built-in progress updates supportWorkaround: use LocalBroadcastManager.In the Loader:
@Overridepublic Result loadInBackground() {
// Show progress barIntent intent = new Intent(LOADING_ACTION).putExtra(LOADING_EXTRA, true);LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
try {return doStuff();
} finally {// Hide progress barintent = new Intent(LOADING_ACTION).putExtra(LOADING_EXTRA, false);LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
}}
![Page 54: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/54.jpg)
Loaders limitations2. No error handling in LoaderCallbacks.You usually simply return null in case of error.Possible Workarounds:1) Encapsulate the result along with an Exception in a composite Object like Pair<T, Exception>.Warning: your Loader's cache must be smarter and check if the object is null or contains an error.
2) Add a property to your Loader to expose a catched Exception.
![Page 55: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/55.jpg)
Loaders limitationspublic abstract class ExceptionSupportLoader<T> extends LocalCacheLoader<T> {
private Exception lastException;
public ExceptionSupportLoader(Context context) {super(context);
}
public Exception getLastException() {return lastException;
}
@Overridepublic T loadInBackground() {
try {return tryLoadInBackground();
} catch (Exception e) {this.lastException = e;return null;
}}
protected abstract T tryLoadInBackground() throws Exception;}
![Page 56: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/56.jpg)
Loaders limitations2. No error handling in LoaderCallbacks.Workaround #2 (end)Then in your LoaderCallbacks:
@Overridepublic void onLoadFinished(Loader<Result> loader, Result result) {
if (result == null) {Exception exception = ((ExceptionSupportLoader<Result>) loader)
.getLastException();// Error handling
} else {// Result handling
}}
![Page 57: Android Loaders : Reloaded](https://reader033.vdocuments.mx/reader033/viewer/2022042518/554a3352b4c90526578b554e/html5/thumbnails/57.jpg)
The End
We made it!Thank you for watching.
Questions?