I'm implementing network communication using loaders... because most of them recommend it and "they are lifecycle aware".
But in my case I've extended the AsyncTaskLoader overridden the loadInBackground() etc.etc.
And at the end the onLoadFinished() is called normally.
But my problem occurs in case of a network error. If the data is null
the onLoadFinished() is called immediately, without the network call.
As per the google docs:
In either case, the given callback is associated with the loader, and
will be called as the loader state changes. If at the point of call
the caller is in its started state, and the requested loader already
exists and has generated its data, then callback
onLoadFinished(Loader, D) will be called immediately (inside of this
function), so you must be prepared for this to happen.
So is this something I've to handle, keep a track using flag and next time call restartLoader() in my activity instead of initLoader(). Or I'm doing something wrong.
I'm posting important parts of my code below:
In Main Activity:
protected void postJson(int loaderId, Parcelable object, boolean loadOffline) {
Bundle bundle = new Bundle();
bundle.putParcelable(Constants.OBJ, object);
bundle.putBoolean(Constants.OFFLINE, loadOffline);
loaderManager.initLoader(loaderId, bundle, this);
}
NetworkAsyncTaskLoader
#Override
public Result loadInBackground() {
Constants.verbose("loadInBackground Called");
.....
Response tempResult;
try {
tempResult = loadFromNetwork();
........
} catch (IOException e) {
Constants.error("Network Error for loader Id: " + loaderId, e);
return null;
}
}
#Override
public void deliverResult(Result result) {
Constants.verbose("deliverResult called");
super.deliverResult(result);
}
onLoadFinished() in Activity
#Override
public void onLoadFinished(Loader loader, NetworkAsyncTaskLoader.Result data) {
hideProgress();
Constants.debug("Load finished for: " + loader.getId());
.....
}
And below is the stack trace..
07-31 10:08:02.177 27677-28061/com.sample.test D/OkHttp: <-- HTTP FAILED: java.net.ConnectException: Failed to connect to ...
07-31 10:08:02.179 27677-28061/com.sample.test E/Test: Network Error for loader Id: 101
java.net.ConnectException: Failed to connect to ...
07-31 10:08:02.192 27677-27677/com.sample.test V/Test: deliverResult called
07-31 10:08:02.193 27677-27677/com.sample.test D/Test: Load finished for: 101
07-31 10:08:08.982 27677-27677/com.sample.test D/Test: Load finished for: 101
As you can see next time I call initLoader() or try to initiate network call.. It directly calls onLoadFinished()
Update:
Well I ended up by creating a flag which sets to false if there is network error or something goes wrong and then if flag is true call restartLoader() instead.
Once you call deliverResult() the data is "pushed out" to the onLoadFinished() callback method, This is as designed.
If you call deliverResult(null) you're basically saying that your results are empty, from the LoaderManager point of view - your Loader finished his job, he "doesn't care" about the null value.
TL;DR - Yes, You need to handle the null result yourself and call restartLoader() to make your Loader run again.
Related
I want to establish a good understanding of the AsyncTaskLoader lifecycle. I checked several resources, everything is clear but the usage of deliverResult. According to this picture from the internet (available here):
onStartLoading will be called, then if there is data already loaded, deliverResult is called, then it deliver the result to onLoadFinished. However, if there is no data foceLoad will be called, then loadInBackground, then deliverResult, then onLoadFinished.
I did the same way croworc answer suggests here: What does AsyncTaskLoader.deliverResult() actually do?
This is the code:
public class WeatherLoader extends AsyncTaskLoader<List<Weather>> {
List <Weather> receivedData;
/** Tag for log messages */
private String mUrl;
public WeatherLoader(Context context, String url) {
super(context);
mUrl = url;
}
#Override
protected void onStartLoading() {
if (receivedData == null){
Log.i ("loader ", "No data yet");
forceLoad();
} else {
deliverResult(receivedData);
Log.i ("loader ", "data is available no reload");
}
}
#Override
public void deliverResult(List<Weather> data) {
receivedData = data;
super.deliverResult(data);
Log.i ("loader ", "deliver result");
}
#Override
public List<Weather> loadInBackground() {
Log.i ("loader ", "load in background");
if (mUrl == null) {
return null;
}
// Perform the network request, parse the response, and extract a list of earthquakes.
List<Weather> weather = getweatherData(mUrl);
return weather;
}
}
But this is the sequence of the callbacks I'm getting when I initialize the loader or restart it:
onCreatLoader
No data yet
load in background
onLoaderFinish
deliver result
What really confuses me is that deliverResult is called after onLoaderFinished which also I think contradicts with this page of this book:
available here
The check for the availability of the data used in onStartLoading which calls deliverResult only gets called when the activity is stopped and restarted, like if I navigate to another activity then get back to it. Here is what gets printed in the logcat in this case:
deliver result
data is available no reload
Even onLoadFinished doesn't get called in this case. However, if I do the same behavior of navigating to another activity and getting back to the first one with having deliverResult with its original behavior (where I only call the super version of it), onStart gets called, then loadInBackground, then onLoadFinished, then DeliverResult. So, a new load happens
Can anyone please clarify why this behavior of callbacks is taking place? Does this mean that the image that shows the lifecycle is inaccurate?
Thanks.
Put the log calls before calling super and check the flow sequence again.
onLoadFinished is called during the call to super.deliverResult.
#Override
public void deliverResult(List<Weather> data) {
Log.i ("loader ", "deliver result");
receivedData = data;
super.deliverResult(data);
}
i've got really weird problem. I'm trying to use Robospice for not-networking task (JSON serialization/deserialization).
I've implemented SpiceService (based on offline example from Robospice github) and Requests - doing serialization stuff in loadDataFromNetwork().
I start and stop SpiceManager in OnStart() and OnStop().
My problem is that request is executing but listener is not fired (None of OnRequestSuccess and OnRequestFailure methods are fired)
Here is the line of code responsible for executing task
manager.execute(saveRequest, new SaveRequestListener());
and my listener
private class SaveRequestListener implements com.octo.android.robospice.request.listener.RequestListener<Boolean> {
#Override
public void onRequestFailure(SpiceException spiceException) {
Log.d("saving",spiceException.getMessage());
Toast.makeText(getActivity(),"Some error :(((", Toast.LENGTH_SHORT).show();
}
#Override
public void onRequestSuccess(Boolean aBoolean) {
Log.d("saving","Saved!!! " + aBoolean);
Toast.makeText(getActivity(),"Saved!!! " + aBoolean, Toast.LENGTH_SHORT).show();
}
}
Is there any chance that you are stopping the SpiceManager before expecting the callback?
In that case, the request will finish as it has already been started, but the RequestListener will never be called since the request listeners are automatically detached. It works this way in order to make sure contexts will not be leaking.
Try ctrl+i or ctrl+o in android studio to reimplamentate the methods. And check the input and output types of methods.
I have an AsyncTask connecting to a websocket.
protected Void doInBackground() {
client.connect();
return null;
}
When it's finished attempting the connection, I want the following to happen (currently inside onPostExecute):
protected void onPostExecute() {
if (socketConnected) {
doOtherThings();
} else {
log("Failed to connect.");
}
I've also tossed in the following, as another probe of sorts (in the WebSocketClient implementation):
public void onOpen() {
log("Opened successfully!");
socketConnected = true;
}
The onPostExecute method prints the failure message , followed by the success message from onOpen. This suggests that doInBackground is returning too soon. Is there any common reason this might happen?
Yes, probably the method client.connect() creates a ASyncTask and this leaves your task to continue executation, ending the method and going to onPostExecute.
If thats the case, the API you are using does should have some methods to listening async messages.
The API should have some Listener or Callback that will be fired at your thread.
EDIT: I searched WebViewClient on internet and the method connect() does indeed creates a new Thread, you should look how to use the calls properly.
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.
I'm using AsyncTask to populate SQLite database. I'm downloading data from a certain webpage and putting it in SQLite tables. The thing is, I want to either download 100% of the data or none. So in case the AsyncTask is for some reason interrupted, I want to delete all the data that has been downloaded so far.
This is how I tried to do it:
#Override
protected void onCancelled() {
super.onCancelled();
dbHandler.deleteFromDatabase(razred);
Log.i("TAG", "AsyncTask cancelled");
}
I thought that "onCancelled" will execute if AsyncTask is interrupted in any way but it doesn't. What could I do to erase data that was made with AsyncTask in case it is cancelled in any way? (ex. activity paused, activity destroyed, internet connection interrupted etc.)
You're on the right track, but in your doInBackground() you also need to specifically call isCancelled() to check if it's cancelled and then return from doInBackground(). Then your code will work properly.
Refer to the AsyncTask documentation for "Cancelling a task"
Here's the quote from the documentation for easy reference:
A task can be cancelled at any time by invoking cancel(boolean). Invoking this method will cause subsequent calls to isCancelled() to return true. After invoking this method, onCancelled(Object), instead of onPostExecute(Object) will be invoked after doInBackground(Object[]) returns. To ensure that a task is cancelled as quickly as possible, you should always check the return value of isCancelled() periodically from doInBackground(Object[]), if possible (inside a loop for instance.)
EDIT: Per request, some sample code:
private class MyAsyncTask extends AsyncTask<Void,Void,Void> {
private SQLiteDatabase db;
#Override
protected void onPreExecute() {
// any kind of initialization or setup needed before the
// background thread kicks off. remember: this is still on
// on the main (UI) thread
// since youre doing DB I/O, Ill make believe Im initializing the DB here
db = DatabaseHelper.getInstance(MainActvity.this).getWritableDatabase();
}
/*
* The background thread to do your disk and network I/O. If you need
* to pass in any parameters, this is the first Void in the template
*/
#Override
protected Void doInBackground(Void... params) {
// other stuff you need to do in the background. Since you want an
// all-or-nothing type thing, we will use a transaction to manually
// control the db
db.beginTransaction();
try {
// do network I/O to retrieve what you need and then write to DB.
...
... // if theres a loop in here somewhere when reading the data, check !isCancelled() as part of the condition or as one of the first statements and then break
...
db.setTransactionSuccessful(); // assuming everything works, need to set
// this successful here at the end of the try
} catch (InterruptedException ie) { // or some other exception
cancel(true); // heres where you can call cancel() if youve been interrupted
} catch (IOException ioe) { // if your network connection has problems
cancel(true);
} finally {
db.endTransaction();
// other cleanup, like closing the HTTP connection...
// no need to close the DB if you implement it properly
}
return null; // if there was some return value, that would go here
}
#Override
protected void onCancelled(Void result) {
// depending on how you implement doInBackground(), you may not even need this,
// unless you have a lot of other "state" you need to reset aside from the DB transaction
}
#Override
protected void onPostExecute(Void result) {
// any other items to do on main (UI) thread after doInBackground() finishes
// remember, this only gets called if cancel() is not called!
}
}
Hope that helps!
I know this is not exactly what you've asked for, but I have to say you are doing it all wrong by using the AsyncTask.
There are many cases where your async task will be terminated without you being able to do anything. For such critical tasks as this one, use a Service.
With a Service you can till the system to restart your service in case it is terminated prematurely. You then can continue what you started, or start all over again (deleting all previous downloads...etc).
With an AsyncTask, if the system decided to terminate your async task prematurely, you are not notified nor the AsyncTask is restarted. It just dies in complete silence.
I think in the onpostexecute you could handle anything you wanted to.
private class ParseDownload extends AsyncTask<Summary, Void, Boolean> {
#Override
protected Boolean doInBackground(Summary... urls) {
for (Summary url : urls) {
url.dosomething();
if (isCanceled();) { return false;}
}
return true;
}
#Override
protected void onPostExecute(Boolean result) {
if (!result) {
// delete * from yourtable here...
// and mark the download incomplete etc.
}
}
}
Good Luck