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.
Related
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;
}
Suppose I have a situation where an API response fills my Musician model with information specific to that musician. There is another API response that, given a musician name, describes related artists.
I want to launch a detail activity showing the musician's name, members, genre, albums(API response A) along with related artists(API response B).
An asynchronous network call will provide a better user experience, but since launching the detail activity is dependent on 2 different api calls, is there a better solution than having API call be asynch and API call B be synch(blocking A and the UI)?
If I make both asynchronous, API call B could be running while the activity changes, stopping it(since an asynctask is destroyed when the activity changes). This would result in missing data in the detail activity.
In a simple case:
Start the detail activity
Execute both requests asynchronously while presenting the user with a loading UX
Once is retrieved, update the UI accordingly
You can change the layout of an Activity at any time at runtime without recreating it.
You can either update the UI immediately with "Response A" and then again with "Response B" or wait for both responses to return and then update the UI; the former is preferable.
Network operations should never block the UI or the user. Displaying loading spinners and such is perfectly acceptable and is used all the time in Google's own apps.
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.
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).
I read a lot about handling rotation in android applications, but I still have so many questions and need to much to understand.
Let me explain my problem or implementation, that I'm using now in my application.
If an activity will be opened, a get request will be sent to server. This request will be executed in a Thread (new Thread(...)) and if request was completed, activity's ui will be refreshed.
But what should I do, if the user rotate his device?
By default, the activity will be destroyed and request will be started again and start a new thread, but the thread of destroyed activity may be still running.
I guess, it's a quite wrong way, I have now.
But what is the best approach, to handle this?
Probably is the best way to forbid rotation, but what If I don't want that?!
May be it's the second part of my question:
I saw a video form Google IO. Mr. Dobjanschi suggested to use services and to store retrieved data in content provider. So, probably I can use a service for executing my requests. But should data be replaced every time the get request was completed?!
Well dont know exactly how its done, You can try saving the instance and retrieving the same when config changes with following methods:
I have read about them but haven't really implemented them yet. I hope it can give you some start.
#Override
public Object onRetainNonConfigurationInstance() {
return(myServerThread);
}
private void restoreServerFunctions() {
if (getLastNonConfigurationInstance()!=null) {
myServerThread=(Thread)getLastNonConfigurationInstance();
}
}
You can specify that the activity handles the rotation itself. This is done through adding:
android:configChanges="keyboardHidden|orientation"
in the tag of the activity inside your android manifest. You don't have to actually handle the rotation but this will tell android to not destroy your activity. The base activity class will handle all the rotating of the user interface for you and your thread will be executed correct.
A small side note: if you are doing only a small server task use AsyncTask to execute the call to the server in the background instead of creating a thread. This will minimze some of the programming effort you need to communicate the results from the thread to the activity and update your UI.
One easy way, though I've never tried it. Instead of refreshing the current UI, when the thread finishes, start a new Activity with the just downloaded content. So first, you start an Activity with a blank page (or just the page's frame), then you rotate the blank page as much as you like, then the downloading Thread spawns a new Activity, replacing the blank page Activity with the loaded content page using the current orientation.