In the click handler for a button, I'm loading some data from a content provider (using getContentResolver().query(...)), then sending that data off in a network request. Since the query happens on the main thread with this approach, I want to move this off the main UI thread.
I think I can use a LoaderManager, and fire off the network request in onLoadFinished(), but the problem is that I don't want onLoadFinished() called ever again (for that Loader id), because I don't want to fire the network request again, during a screen orientation for example.
So, how do I use a LoaderManager for a query that I only want to happen only once?
Calling LoaderManager#initLoader() in your Activity#onCreate() method will either create a new Loader and force a new load, or reuse an existing Loader and deliver the most recently queried data if any exists. So as long as you are using the LoaderManager correctly (i.e. the way the developer's site recommends in the documentation), you shouldn't have any problems.
In your onLoadFinished(), you can call getLoaderManager().destroyLoader(loaderId) (or getSupportLoaderManager() as applicable). That will stop the Loader from automatically reloading. You may also need to ensure that where you are calling initLoader is guarded from executing again (via saving a boolean variable in onSaveInstanceState for example).
Related
My AsyncTask for fetching data is a long one, and at the same time, in the midst of constructing my recyclerview I wanted to check if a cursorloader was able to query from my content provider correctly.
Bear with me here, I used Loader Callbacks interface and onLoadFinished to get results. Since the asynctask is still running, the loader still calls the content provider as it gets updated until asynctask ends. I would except that since onLoadFinished primarily deals with filling in the contents of an adapter with cursor data that I shouldn't worry that it prints out log statements simultaneously as the asynctask continues to run, but I wanted to confirm.
I do intend to eventually move this asynctask into an intentservice that only gets called via broadcast.
You can call getLoaderManager().restartLoader(URL_LOADER, null, HomeFragment.this); to restart the loader on you AsyncTask onPostExecute method. You need to init the Loader first on you onCreate() method.
This way, the loader will refresh with the same projection it had when you started it and it will fetch the results of the AsyncTask operations from DB.
I have a FragmentActivity with 7 tabs, and all of them refers to the same fragment, the only difference is a parameter, that makes them to load throught an ASyncTask the data to show from a PHP that returns a JSON. My problem is that when I swipe from one tab to another, if the task from the first tab is still loading, it loads in the new tab, or crash, or doesnt do anything. However, the activity load two tabs, so the task is launched twice and is the same problem. Any idea?
While AsyncTasks are wonderful to have, they are intended to be procedures that are independent of any UI (e.g. saving information). For the longest time I was in the same boat and used AsyncTasks for work that would end up changing the UI (since hey, they have an onPost method).
What you should be using for any work that will affect the UI is called a Loader which will pay attention to the UI state of the Fragment. In your case the AsyncTask is probably attempting to access a UI element that no longer exists (View Pagers only keep the previous, current, and next views in memory). The Loader will pay attention to this and not attempt to change the UI.
There are plenty of examples out on the web, but in short you will need to create (extend) a Loader for each of your AsyncTasks (I recommend AsyncTaskLoader, if you do pay attention to forceLoad) and add the callbacks (LoaderManager.LoaderCallbacks) to your Fragment. Then when you are ready to load call getLoaderManager().restartLoader(LOADER_ID, bundle_args, loader_callback);
Keep a reference of your AsyncTask. I assume you have a callback which let's you know when the tabs have changed. When you get notified that tabs have changed you can check if your AsyncTask is null or not finished yet, if it isn't you call it's cancel() method.
if(asyncTask!=null && asyncTask.getStatus()!=AsyncTask.Status.FINISHED) {
asyncTask.cancel(false);
asyncTask = null;
}
I am trying to understand some finer points of AsyncTaskLoaders. This may be obvious, to others but I can't find an unambiguous example or definition that demonstrates and exmplains what happens when you override the deliverResult() method. What actually gets delivered ? How does this interact with the calling object ? I can see use of super.deliverResult, which passes a private object from the class. So, does the loader automatically know what to associate with the "delivered result". I am totally confused.
Seems I'm a bit late to the party, but anyway...
One of the main advantages of this intermediary step between the background loading and the UI thread's callback onLoadFinished() getting called
loadInBackground()
deliverResult() and
the callback onLoadFinished()
is that it gives us a means of shortcutting the whole loading process from within the AsyncTaskLoader class.
And this can be put to good use for caching the loading result within your AsyncTaskLoader and preventing the background loading from happening if there is cached data.
And why would we want to do this? Isn't the whole point of loaders dealing with those dreaded activity lifecycle issues (e.g. rotating the device), maintaining state (like, caching data) and having a means to get updated when underlying data changes (CursorLoader)?
Well, yes, but this isn't the whole story.
Consider this use case:
You've got your app (the one with the AsynTaskLoader) up-and-running and it already has loaded data into your UI.
Then, you switch over to your Twitter app to check on some news and return to you app.
Without caching, upon returning to your app, the loader would do its reloading.
This behavior is different from the one after configuration changes, e.g. rotating your device, in which case no reloading would take place.
So, how would we then prevent the loader from re-fetching data in case we're just sending our app to the background and, later, return to it again?
Solution
Create a cache member variable in your AsyncTaskLoader implementation.
Override deliverResult() so that you save your fetched data in your cache first, before you call the superclass's implementation of deliverResult().
In onStartLoading() check if there's cached data, and if so, let your AsyncTaskLoader just deliver that. Otherwise, start loading.
Here's a link to a sample app which implements this behaviour.
It's just a "Toy app" and as such part of Udacity's current version of the "Developing Android Apps" fundamentals course. And here is the link to the respective video within that course that deals with this issue. (The course is free, but you'll still have to sign-up w/ Udacity).
In short, what this app demonstrates, is a UI in which the user can input a search query for searching GitHub's repos (via the GitHub API), showing the resulting search URL in a TextView and also the raw JSON fetched from GitHub in another TextView.
The whole action happens in just MainActivity.java and the relevant part here is within the AsyncTaskLoader that's implemented as an anonymous inner class:
For step 1, just introduce a member variable in your AsyncTaskLoader implementation that's meant to serve as your data cache.
/* This String will contain the raw JSON
from the results of our Github search */
String mGithubJson;
For step 2, override deliverResult() as to cache the loading result.
When loadInBackground() has finished, it passes its return value to deliverResult().
It does so anyway, but now that we've overridden deliverResult() we can step right in and store our fetched data into the cache member variable which we've created with just so good foresight.
And finally, we chain up to the super class implementation of deliverResult() with super.deliverResult() which will pass-on the result to the callback method onLoadFinished(), running on the UI thread.
#Override
public void deliverResult(String githubJson) {
mGithubJson = githubJson;
super.deliverResult(githubJson);
}
For step 3, check in onStartLoading() whether or not we've got cached data.
If we don't have cached data (yet), just force the loading to begin with a call to forceLoad().
But if we do have cached data, just call deliverResult(yourCachedDataGoesHere) and pass-in the cached data as argument.
if (mGithubJson != null) {
deliverResult(mGithubJson);
} else {
forceLoad();
}
So, if you now switch back and forth between your app and some other app(s), you'll notice that no reloading takes place, as the loader will just use your cached data.
suppose when data are loading in the background, at this time, user press HOME button and exist the app, when user comes back to the app, loading has been finished. So we have already have the data, then AsyncTaskLoader will call the deliverResult() method, deliver the data to the onLoadFinished() method for displaying.
When the user come back to app, onStartLoading() is being called before loadInBackground(). In this method, we could check if our data if empty or not, if not empty, we call deliverResult() and send the result to onLoaderFinished(), so it could prevent to reload data.
When we press HOME exist the app and then come back, it will not create a new Loader, instead the old loader will try to load data.
The only answer I can find that makes any sense is based on a decription in this link.
"A registered listener to receive the Loader's results when it
completes a load. For each of its Loaders, the LoaderManager
registers an OnLoadCompleteListener which will forward the Loader’s
delivered results to the client with a call to
onLoadFinished(Loader loader, D result). Loaders should deliver
results to these registered listeners with a call to
Loader#deliverResult(D result)."
deliverResult appears to be used when you have listeners to the AsyncTask and want to send the results back to them. I would say it's uncommon. The Android documentation is even less descriptive:
"Sends the result of the load to the registered listener. Should only
be called by subclasses. Must be called from the process's main
thread.
Parameters
data : the result of the load"
deliverResult works after doInbackground completes. It sends the result D (returned by doInBackground) to the calling thread. You may wish to override it for cleaning data, but you can do clean-up in doInBackground instead without overriding deliverResult.
from the API it says that one of the loaders characteristics is :
They monitor the source of their data and deliver new results when the
content changes.
My question is, how exactly does it do that? can provide me some tutorial or code.. or some kind of explanation ?
Here is a good answer on SO:
Custom CursorLoader notify data change
Basically you need to register your loader for a callback when the data changed, and then act in it.
I also think this tutorial covers it pretty well:
http://www.androiddesignpatterns.com/2012/08/implementing-loaders.html (Check the 'What Makes Up a Loader?' section)
Yes. I following this tutorial
And the documentation say:
In either case, the given
LoaderManager.LoaderCallbacks implementation is associated with the
loader, and will be called when the loader state changes. If at the
point of this call the caller is in its started state, and the
requested loader already exists and has generated its data, then the
system calls onLoadFinished() immediately (during initLoader()), so
you must be prepared for this to happen. See onLoadFinished for more
discussion of this callback
Note that the initLoader() method returns the Loader that is created,
but you don't need to capture a reference to it. The LoaderManager
manages the life of the loader automatically. The LoaderManager starts
and stops loading when necessary, and maintains the state of the
loader and its associated content. As this implies, you rarely
interact with loaders directly (though for an example of using loader
methods to fine-tune a loader's behavior, see the LoaderThrottle
sample). You most commonly use the LoaderManager.LoaderCallbacks
methods to intervene in the loading process when particular events
occur. For more discussion of this topic, see Using the LoaderManager
Callbacks.
I am developing an application in which several fragments are involved.
In each fragment I have to call web service to fetch data.
Currently I am calling web service from onCreateView() method of Fragment. Issue i am getting that whenever web service call is in progress and if device orientation is changed then new web service call starts invoking.
I think this might be because onCreateView() method gets called on configuration change.
How can I solve this. and which Life cycle method should I use to call web service so that it will be get called only once
I have resolved this by following workaround
Create an operation identifier for each web service call method. E.g. for example "Authentication" for login call
Create one object of ArrayList say currentTasks
ArrayList<String> currentTasks = new ArrayList<String>();
In every method where I am calling web service, check if operation identifier of corresponding method is already present in ArrayList. If not then start operation.
String operationId = "Authentication";
if(currentTasks.indexOf(operationId) == -1)
{
<do web service call operation here>
currentTasks.add(operationId);
}
Method in which above operation's response is receiving, remove operation identifier from ArrayList
if(currentTasks.indexOf("Authentication") != -1){
currentTasks.remove("Authentication");
}
This will ensure that call will not go to web method which is currently in progress.
I know this is not the best way to achieve it and this might not the best practice to follow but for now this works for me.
If your web service code is getting restarted due to a device orientation change, than you're probably using an AsyncTask. This is a common problem with AsyncTasks and you have several ways to solve it.
One common workaround is to wrap your AsyncTask around an invisible Fragment and make sure this fragment does not get destroyed and recreated again during an orientation change. Check out this tutorial on how to do it: Retaining Objects Across Config Changes
A simple solution would be to use a static variable pointing to asynctask. Create and call an AsyncTask if variable is not null.
Regards,
Prateek