I'm having a couple of problems with my AsyncTaskLoader, not sure if they're related as they both happen when attempting to restart the Loader. In my application I have 3 instances of a custom CursorAdapter, backed by 3 instances of a custom AsyncTaskLoader managed by 1 singleton LoaderManager. The problems relate to two differenct Adapter/Loader pairs, but the code used is the same in each case:
getLoaderManager().restartLoader(loaderId, bundle, loaderManager);
Problem 1: I call restartLoader() and the LoaderManager registers a call to onCreateLoader, but not one to onLoaderReset(). The Loader gets to deliverResult(), but onLoadFinished() is never called. The Loader has neither the 'reset' or 'started' flags set (see code below).
Problem 2: I call restartLoader() and the LoaderManager registers a call to onLoaderReset(). The Loader gets to onReset(), but doesn't get any further. The Cursor is set to null, but no new Cursor is loaded.
Any ideas what the problem could be? Here's some of the code for the Loader and Loader Manager:
CustomCursorLoader.java
#Override
protected void onStartLoading() {
Log.v(TAG, "Starting Loader");
if (lastCursor != null) {
deliverResult(lastCursor);
}
if (takeContentChanged() || lastCursor == null) {
forceLoad();
}
}
#Override
public void deliverResult(Cursor cursor) {
Log.v(TAG, "Delivering result");
if (isReset()) {
Log.v(TAG, "reset");
if (cursor != null) {
cursor.close();
}
return;
}
Cursor oldCursor = lastCursor;
lastCursor = cursor;
if (isStarted()) {
Log.v(TAG, "started");
super.deliverResult(cursor);
}
if (oldCursor != null && oldCursor != cursor && !oldCursor.isClosed()) {
oldCursor.close();
}
}
#Override
protected void onReset() {
Log.v(TAG, "Reset");
super.onReset();
onStopLoading();
if (lastCursor != null && !lastCursor.isClosed()) {
lastCursor.close();
}
lastCursor = null;
}
CustomCursorLoaderManager.java:
#Override
public Loader<Cursor> onCreateLoader(int loaderId, Bundle bundle) {
return new CustomCursorLoader();
}
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
cursorAdapter.changeCursor(cursor);
}
#Override
public void onLoaderReset(Loader<Cursor> loader) {
cursorAdapter.changeCursor(null);
}
What you are calling a 'LoaderManager' is actually an implementation of the LoaderManager.LoaderCallbacks<D> interface. You might want to use a different name, this one is confusing. Why is it a singleton? It usually is tied to an Activity or Fragment, possibly just the Activity/Fragment implementing the interface. Where are you creating your Loaders (activity/fragment)? Also make sure you call LoaderManager.initLoader() from onCreate()/onActivityCreated(), otherwise the loader may not be started properly.
When you create a cursor and point it at a database, you can't just set it to null. You have to explicitly close the cursor, or it will lock the database until it times out.
I recommend taking advantage of the Android lifecycle and your existing callbacks to implement this fix.
Hope this helps!
Related
I have a fragment where I'll do the following:
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
}
});
Here I get for runOnUiThread a warning may produce NullPointerException.
The code works without problems.Android Studio suggests I change the code like this:
Objects.requireNonNull(getActivity()).runOnUiThread(new Runnable() {
#Override
public void run() {
is that sensible ? is there any other/better way ?
It depends on what your objective is :
1) You want the method caller to know he made a mistake by calling this method at a wrong state where getActivity()
private void myMethod() {
if(null == getActivity()) {
throw new IllegalStateException("Trying to call getActivity() at a state where the Activity equals null"); // Or new NullPointerException("")
}
// Do your stuff with getActivity()
}
2) You know that getActivity() will not throw a NullPointerException in your case :
private void myMethod() {
assert getActivity() != null;
// Do your stuff with getActivity()
}
3) You know that getActivity() may be null, you don't want the app to suddenly stop :
private void myMethod() {
if(null == getActivity()) {
return;
}
// Do your stuff with getActivity()
}
Using Objects.requireNonNull() also requires api level 19 (4.4 (KITKAT))
You also have tons of information right here
You could just add a null check, which may be more understandable.
if(getActivity() != null) {
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
}
});
}
But also, you can use the method #requireActivity()
This works as a getActivity but if the activity is null (if the fragment is detached) it will throw an IllegalStateException
https://developer.android.com/reference/android/support/v4/app/Fragment#requireActivity()
(1) Use Flag isAdded(fragment is added to host activity) before getActivity(). This helps to avoid null pointer exception if the fragment is detached from the host activity.
if (isAdded() && null != getActivity()) {
// your logic
}
(2) requireNonNull(T obj) Checks that the specified object reference is not null. This method is designed primarily for doing parameter validation in methods and constructors.
Throws NullPointerException - if obj is null(application execution may terminate at this point).
Summary: As per your current context requireNonNull(T obj) is not suitable. You must handle null pointer gracefully.
I'm developing an app where I need to do a network call every 30 seconds, and delete the previous data and insert the new one. And every time the new data is inserted I'm showing it in the RecyclerView. I'm using Handler to give a network call and LiveData for observing data changes. Everything just works fine, just Live data observer triggers multiple time, so the data is getting deleted and inserted multiple times in result to refresh the RecyclerView frequently causing it to flash multiple times every 30 seconds.
So below is the code what I've tried:
In my Fragment I do this:
private LiveData<List<RestaurantTablesModel>> mData;
private Observer<List<RestaurantTablesModel>> mObserver;
private TablesViewModel mViewModel;
#Override
public View onCreateView(#NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View mView = inflater.inflate(R.layout.fragment_tables, container, false);
ButterKnife.bind(this, mView);
TablesViewModelFactory factory = InjectorUtils.provideTablesFactory(getActivity());
mViewModel = ViewModelProviders.of(this, factory).get(TablesViewModel.class);
setUpUserRecyclerView();
return mView;
}
private void setUpRecyclerView() {
mData = mViewModel.getTablesData(mLocationID);
mObserver = tablesModels -> {
if (tablesModels != null) {
mTablesRecyclerAdapter.addTables(tablesModels);
Log.e(LOG_TAG, "setUpUserRecyclerView: tablesModels");
}
};
mData.observe(this, mObserver);
}
Removing the observer onDestroy:
#Override
public void onDestroy() {
mData.removeObserver(mObserver);
super.onDestroy();
}
Following is my method in ViewModel:
public LiveData<List<TablesModel>> getTablesData(int mLocationID){
return mRepository.getTablesData(mLocationID);
}
Repository:
public LiveData<List<TablesModel>> getTablesData(int mLocationID){
LiveData<TablesModel[]> mTablesData = mDataSource.getTablesData();
mTablesData.observeForever(tablesModels -> {
mExecutors.diskIO().execute(() -> {
//Completed: delete old table data if there are conflicts.
if (tablesModels != null) {
mDatabaseDao.deleteTables();
mDatabaseDao.insertTablesData(tablesModels);
}else {
Log.e(LOG_TAG, "Nothing: ");
}
});
Log.e("Handlers", "repository getTablesData");
});
return mDatabaseDao.getTablesData(mLocationID);
}
DataSource:
private MutableLiveData<RestaurantTablesModel[]> mDownloadedTablesModel;
public LiveData<RestaurantTablesModel[]> getTablesData() {
Log.e("Handlers", "getTablesData");
fetchTablesData();
return mDownloadedTablesModel;
}
public void fetchTablesData() {
if (Utils.isNetworkAvailable(mContext)) {
NetworkUtils.NetworkInterface mInterface = this;
handler = new Handler();
runnableCode = new Runnable() {
#Override
public void run() {
// Do something here on the main thread
Log.e("Handlers", "Called on network thread");
URL getTablesURL = NetworkUtils.getAllTableUrl(mContext);
NetworkUtils.getResponseFromAPI(mContext, getTablesURL, mInterface);
// Repeat this the same runnable code block again another 30 seconds
// 'this' is referencing the Runnable object
handler.postDelayed(this, 30000);
}
};
handler.post(runnableCode);
} else {
Log.d(LOG_TAG, "fetchTablesData: No network!");
}
}
Now the problem is when my fragment is destroyed and recreated the Observer gets triggered multiple times, here are the logs:
09-05 10:28:29.853 3666-3666/? E/TablesFragment: setUpRecyclerView: tablesModels
09-05 10:28:30.039 3666-3666/? E/TablesFragment: setUpRecyclerView: tablesModels
09-05 10:28:30.607 3666-3666/? E/TablesFragment: setUpRecyclerView: tablesModels
09-05 10:28:30.657 3666-3666/? E/TablesFragment: setUpRecyclerView: tablesModels
09-05 10:28:30.669 3666-3666/? E/TablesFragment: setUpRecyclerView: tablesModels
09-05 10:28:30.704 3666-3666/? E/TablesFragment: setUpRecyclerView: tablesModels
And it triggers more times than before, every time fragment is recreated, I think it the observer is getting called on a recreation of the fragment and the previous instance of the observer is still in a play.
But if I'm removing the observer in OnDestroy, why should it be happening?
Any help would be highly appreciated.
EDIT:
I changed my code to check if the LiveData and Observer are null, and then only initialize it. But it doesn't help, it is still getting called multiple times.
if (mTablesData == null){
mData = mViewModel.getTablesData(mLocationID);
if (mObserver == null){
mObserver = tablesModels -> {
if (tablesModels != null) {
mTablesRecyclerAdapter.addTables(tablesModels);
Log.e(LOG_TAG, "setUpUserRecyclerView: tablesModels");
}
};
mData.observe(this, mObserver);
}
}
EDIT 2:
Tried this also, but didn't work as well:
mTablesData = mViewModel.getTablesData(mLocationID);
mObserver = tablesModels -> {
if (tablesModels != null) {
mTablesRecyclerAdapter.addTables(tablesModels);
Log.e(LOG_TAG, "setUpRecyclerView: tablesModels");
}
};
if (!mTablesData.hasObservers()) {
mTablesData.observe(this, mObserver);
}
So what we learned from experiments in the comments, you needed to check if mTablesData is already being observed before observing it, and observe only if it is not being observed, like
if (!mTablesData.hasObservers()) {
mTablesData.observeForever(tablesModels -> {
...
First, If I understand correct you use RecyclerView and every fragment in that RecyclerView called setUpUserRecyclerView(); in its onCreate() method. So, if you have 3 Fragments, you will have 3 Observers. If you want all of them to use the ViewModel of the Activity you have to point the parent Activity here -> ViewModelProviders.of(getActivity(), factory)
Second, Why do you use observeForever in your Repository? Can you use just observe?
And last if you want to run this request in every 30 seconds, why don't you use PeriodicWorkRequest of WorkManager -> https://developer.android.com/topic/libraries/architecture/workmanager/basics#java
Hope I help somehow :)
I think you need wrap the mObserver in a CompositeDisposable.
CompositeDisposable disposable = new CompositeDisposable();
disposable.add(mObserver);
#Override
public void onDestroy() {
mData.removeObserver(mObserver);
disposable.clear();
super.onDestroy();
}
I hope it helps you.
I am creating an AsyncTaskLoader based on http://developer.android.com/reference/android/content/AsyncTaskLoader.html. When I run my app, according to my logging, the app endlessly oscillates between loadInBackground and onCanceled. Does anyone know why this error might happen? My BroadcastReceiver is based on Proper notification of AsyncTaskLoader about data changes from background thread.
Here is my loadInBackground method:
#Override
public List<MyItem> loadInBackground() {
List<MyItem> items = createDummyData();
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(new Intent(RECEIVER_FILTER_STRING));
Log.d(TAG,”custom loader send broadcast from background and send items: "+items.size());
return items;
}
Here is my onStartLoading
#Override
protected void onStartLoading() {
Log.d(TAG, "items loader onStartLoading");
if (null != mData) {
Log.d(TAG, "items loader onStartLoading mData not null");
//someone is calling to start the loader, so if we have data, deliver it now
deliverResult(mData);
}
if (null == mReceiver) {
Log.d(TAG, "items loader onStartLoading register receiver");
mReceiver = new LoaderBroadcastReceiver(this);
LocalBroadcastManager.getInstance(getContext()).
registerReceiver(mReceiver, new IntentFilter(RECEIVER_FILTER_STRING));
}
if (takeContentChanged() || null == mData) {
//if data has changed since the last time it was loaded or is not available, then:
Log.d(TAG, "items loader onStartLoading onChange forceLoad");
forceLoad();
}
}
Here is my Receiver
class LoaderBroadcastReceiver extends BroadcastReceiver {
private Loader loader;
public LoaderBroadcastReceiver(Loader loader) {
this.loader = loader;
}
#Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "loader receiver informing of oonContentChagned");
loader.onContentChanged();
}
}
When it loads in background, it sends the Broadcast, and the Broadcast called onContentChanged() that's why the current Loader is cancelled and rerun again.
You should simply remove the BroadcastReceiver, or the BroadcastReceiver should not call onContentChanged().
From the link you quoted, the BroadcastReceiver is used for something like this, e.g. you loader load the file list in a folder, and for some reason you know that new files is added, so you send the Broadcast (not from your loader) and force the loader to rerun and get new content.
public void onContentChanged () Added in API level 11
Called when Loader.ForceLoadContentObserver detects a change. The
default implementation checks to see if the loader is currently
started; if so, it simply calls forceLoad();
I have a method called fetchData() to fetch some data from the database and load those to a ListView. But when the activity starts there is a small lag because of this. So I need to load the data in background. I was wondering if anyone could tell me how to do this using AsyncTask.
This is my fetchData() method.
public void fetchData() {
database = helper.getReadableDatabase();
Cursor c;
Date cDate = new Date();
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
final String fDate = sdf.format(cDate);
int thisMonth=Integer.parseInt(fDate.split("-")[1]);
Month mn=new Month();
String month=mn.getMonth(thisMonth);
Calendar cal=Calendar.getInstance();
int today=Integer.parseInt(fDate.split("-")[2]);
int curTab=position;
String whereClause="";
String sort="";
if(curTab==0){
whereClause=null;
sort=Database.NAME;
}
else if(curTab==1){
whereClause=Database.MONTH+" = '"+month+"' and "+Database.DAY+" ="+today;
sort=Database.NAME;
}
else if(curTab==2){
cal.add(Calendar.DAY_OF_MONTH, 1);
int monthn=cal.get(Calendar.MONTH)+1;
Month mnN=new Month();
String monthTomorrow=mnN.getMonth(monthn);
int tomorrow=cal.get(Calendar.DAY_OF_MONTH);
whereClause=Database.MONTH+" = '"+monthTomorrow+"' and "+Database.DAY+" ="+tomorrow;
sort=Database.DAY;
}
else if(curTab==3){
whereClause=Database.MONTH+" = '"+month+"'";
sort=Database.DAY;
}
if(DrawerMain.pos==1){
if(curTab==0){
whereClause=Database.TYPE+"='birthday'";
}
else{
whereClause=whereClause+" and "+Database.TYPE+"='birthday'";
}
}
else if(DrawerMain.pos==2){
if(curTab==0){
whereClause=Database.TYPE+"='anniversary'";
}
else{
whereClause=whereClause+" and "+Database.TYPE+"='anniversary'";
}
}
c = database.query(Database.TABLE_EVENT, null, whereClause, null, null, null, sort);
String[] fromDB={Database.NAME,Database.MONTH,Database.DAY};
int[] toView={R.id.tvName_lv,R.id.tv_month_lv,R.id.tv_day_lv};
CustomCursorAdapter adapter=new CustomCursorAdapter(getActivity(), c, 0, R.layout.events_list_item,fromDB,toView);
lv.setAdapter(adapter);
database.close();
}
You should consider using AsyncTaskLoader instead. AsyncLoaders will handle orientation changes better than AsyncTasks.
You can find a tutorial here: http://www.androiddesignpatterns.com/2012/08/implementing-loaders.html
Code (copied directly from the tutorial)
public class SampleLoader extends AsyncTaskLoader<List<SampleItem>> {
// We hold a reference to the Loader’s data here.
private List<SampleItem> mData;
public SampleLoader(Context ctx) {
// Loaders may be used across multiple Activitys (assuming they aren't
// bound to the LoaderManager), so NEVER hold a reference to the context
// directly. Doing so will cause you to leak an entire Activity's context.
// The superclass constructor will store a reference to the Application
// Context instead, and can be retrieved with a call to getContext().
super(ctx);
}
/****************************************************/
/** (1) A task that performs the asynchronous load **/
/****************************************************/
#Override
public List<SampleItem> loadInBackground() {
// This method is called on a background thread and should generate a
// new set of data to be delivered back to the client.
List<SampleItem> data = new ArrayList<SampleItem>();
// TODO: Perform the query here and add the results to 'data'.
return data;
}
/********************************************************/
/** (2) Deliver the results to the registered listener **/
/********************************************************/
#Override
public void deliverResult(List<SampleItem> data) {
if (isReset()) {
// The Loader has been reset; ignore the result and invalidate the data.
releaseResources(data);
return;
}
// Hold a reference to the old data so it doesn't get garbage collected.
// We must protect it until the new data has been delivered.
List<SampleItem> oldData = mData;
mData = data;
if (isStarted()) {
// If the Loader is in a started state, deliver the results to the
// client. The superclass method does this for us.
super.deliverResult(data);
}
// Invalidate the old data as we don't need it any more.
if (oldData != null && oldData != data) {
releaseResources(oldData);
}
}
/*********************************************************/
/** (3) Implement the Loader’s state-dependent behavior **/
/*********************************************************/
#Override
protected void onStartLoading() {
if (mData != null) {
// Deliver any previously loaded data immediately.
deliverResult(mData);
}
// Begin monitoring the underlying data source.
if (mObserver == null) {
mObserver = new SampleObserver();
// TODO: register the observer
}
if (takeContentChanged() || mData == null) {
// When the observer detects a change, it should call onContentChanged()
// on the Loader, which will cause the next call to takeContentChanged()
// to return true. If this is ever the case (or if the current data is
// null), we force a new load.
forceLoad();
}
}
#Override
protected void onStopLoading() {
// The Loader is in a stopped state, so we should attempt to cancel the
// current load (if there is one).
cancelLoad();
// Note that we leave the observer as is. Loaders in a stopped state
// should still monitor the data source for changes so that the Loader
// will know to force a new load if it is ever started again.
}
#Override
protected void onReset() {
// Ensure the loader has been stopped.
onStopLoading();
// At this point we can release the resources associated with 'mData'.
if (mData != null) {
releaseResources(mData);
mData = null;
}
// The Loader is being reset, so we should stop monitoring for changes.
if (mObserver != null) {
// TODO: unregister the observer
mObserver = null;
}
}
#Override
public void onCanceled(List<SampleItem> data) {
// Attempt to cancel the current asynchronous load.
super.onCanceled(data);
// The load has been canceled, so we should release the resources
// associated with 'data'.
releaseResources(data);
}
private void releaseResources(List<SampleItem> data) {
// For a simple List, there is nothing to do. For something like a Cursor, we
// would close it in this method. All resources associated with the Loader
// should be released here.
}
/*********************************************************************/
/** (4) Observer which receives notifications when the data changes **/
/*********************************************************************/
// NOTE: Implementing an observer is outside the scope of this post (this example
// uses a made-up "SampleObserver" to illustrate when/where the observer should
// be initialized).
// The observer could be anything so long as it is able to detect content changes
// and report them to the loader with a call to onContentChanged(). For example,
// if you were writing a Loader which loads a list of all installed applications
// on the device, the observer could be a BroadcastReceiver that listens for the
// ACTION_PACKAGE_ADDED intent, and calls onContentChanged() on the particular
// Loader whenever the receiver detects that a new application has been installed.
// Please don’t hesitate to leave a comment if you still find this confusing! :)
private SampleObserver mObserver;
}
Put your fetchData() method in loadInBackground(). Close your Cursor in the releaseResources() method. In your onCreate() call
getLoaderManager().initLoader(0, null, this);
I have a class that extends AsyncTaskLoader and which frequently receives updates. Now when the app initially starts everything works fine, the UI (a SherlockListFragment (so I am using the compatability library) that implements LoaderManager.LoaderCallbacks<List<Item>>) updates accordingly as updates are received. At some random point however the UI simply stops updating. So far I haven't noticed any type of pattern to ascertain when it will stop updating; it can happen when very few updates are occurring or when many updates are occurring.
Below is my custom AsyncTaskLoader (I simply edited class and variable names to be highly generic so as to hopefully make the code slightly simpler to understand):
public class CustomLoader extends AsyncTaskLoader<List<Item>> {
private static final String LOG_TAG = "CustomLoader";
private List<Item> items;
public CustomLoader(Context context) {
super(context);
}
#Override
public List<Item> loadInBackground() {
return ItemModel.getItemList();
}
#Override
public void deliverResult(List<Item> data) {
if (isReset()) {
if (data != null) {
Log.w(LOG_TAG, "Warning! An async query came in while the Loader was reset!");
releaseResources(data);
return;
}
}
// Hold a reference to the old data so it doesn't get garbage collected.
// We must protect it until the new data has been delivered.
List<Item> oldItems = items;
items = data;
if (isStarted()) {
Log.i(LOG_TAG, "Delivering results to the LoaderManager.");
// If the Loader is in a started state, have the superclass deliver the
// results to the client.
super.deliverResult(data);
}
// Invalidate the old data as we don't need it any more.
if (oldItems != null && oldItems != data) {
Log.i(LOG_TAG, "Releasing any old data associated with this Loader.");
releaseResources(oldItems);
}
}
#Override
protected void onStartLoading() {
Log.i(LOG_TAG, "onStartLoading() called!");
if (items != null) {
// Deliver any previously loaded data immediately.
Log.i(LOG_TAG, "Delivering previously loaded data to the client");
deliverResult(items);
}
//Initialises the loader within the model
ItemModel.registerLoader(this);
if (takeContentChanged()) {
forceLoad();
}
else if (items == null) {
// If the current data is null... then we should make it non-null! :)
forceLoad();
}
}
#Override
protected void onStopLoading() {
Log.i(LOG_TAG, "onStopLoading() called!");
// The Loader has been put in a stopped state, so we should attempt to
// cancel the current load (if there is one).
cancelLoad();
}
#Override
protected void onReset() {
Log.i(LOG_TAG, "onReset() called!");
super.onReset();
// Ensure the loader is stopped.
onStopLoading();
// At this point we can release the resources associated.
if (items != null) {
releaseResources(items);
items = null;
}
// The Loader is being reset, so we should stop monitoring for changes.
// We do this by making the loader instance null
ItemModel.deregisterLoader();
}
#Override
public void onCanceled(List<Item> data) {
Log.i(LOG_TAG, "onCanceled() called!");
/**
* So... we were having problems with the loader sometimes simply not refreshing. It was found
* that when receiving two updates in quick succession, the loader would call onCanceled() after
* the second update (in order to try to stop the previous load). Whenever onCanceled() was called,
* the loader would stop refreshing.
*
* And the reason for this?? The support library version of Loader does not support onCanceled() !!!
* Thanks to this answer on stack overflow for bringing up the issue - http://stackoverflow.com/a/15449553
* By examining the API for the support library and the API 11 versions of Loader, it is clear that
* we shouldn't be receiving onCanceled() calls here, but we still do!
*
* Also important to note is that even on Android 3.0 and up, the framework will still use the
* support library methods for Loader.
*
* So we simply swallow this onCanceled() call and don't call the super method. This seems to fix
* the issue - it may also work if we simply remove onCanceled() completely, but not 100% sure.
*/
// Attempt to cancel the current asynchronous load.
//super.onCanceled(data);
// The load has been canceled, so we should release the associated resources
//Uncommenting this line of code does not resolve my issue
//releaseResources(data);
}
#Override
public void forceLoad() {
Log.i(LOG_TAG, "forceLoad() called!");
super.forceLoad();
}
private void releaseResources(List<Item> data) {
// All resources associated with the Loader should be released here.
if (data != null) {
data.clear();
data = null;
}
}
}
Now, while the UI is still updating properly the Logs show the following sequence of events:
03-03 17:23:33.859: I/CustomLoader(20663): forceLoad() called!
03-03 17:23:33.859: I/CustomLoader(20663): Load in background called...
03-03 17:23:33.864: I/CustomLoader(20663): Delivering results to the LoaderManager.
03-03 17:23:33.864: D/CustomFragment(20663): onLoadFinished() for loader_id 0
03-03 17:23:33.869: I/CustomLoader(20663): Releasing any old data associated with this Loader.
whenever the data is updated.
At the point that the UI stops updating it seems as though forceLoad() keeps on getting called every time the data changes it doesn't seem to actually accomplish anything (i.e. loadInBackground() doesn't get called). I have done a lot of research, looking at other implementations of AsyncTaskLoader and the overall logic of my implementation is similar to everything I've found so I'm at a bit of a loss here.