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);
}
Related
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.
I have over 10 fragments that execute the same kind of task which is :
Retrieving the Data from the server using Retrofit
Starting an Async Task to update the Database (Using ORMLite)
Once the Database is updated, retrieving the new data from the Database
Notify Dataset has changed in the adapter
I'm wondering if it's useless to put the update database code inside an AsyncTask within my fragment once I retrieve the data from the server?
I have trouble understanding what run on the UI thread and what doesn't and should be started as his own thread through an AsyncTask
Here my code:
private void getLocalIncidentTemplate() {
mIncidentTemplate.clear();
mIncidentTemplate.addAll(GenericDAO.getInstance(EntityGroup.class).queryForAll());
Collections.sort(mIncidentTemplate);
Log.e(TAG, "Incident Template count:" + mIncidentTemplate.size());
mListAdapter.notifyDataSetChanged();
spinner.setVisibility(View.GONE);
}
private void getRemoteIncidentTemplate() {
Call<EntityIncident> call = meepServices.getIncidentTemplate();
call.enqueue(new Callback<EntityIncident>() {
#Override
public void onResponse(Call<EntityIncident> call, Response<EntityIncident> response) {
if (response.isSuccessful()) {
new updateIncidentTemplateTask().execute(response.body());
}
}
#Override
public void onFailure(Call<EntityIncident> call, Throwable t) {
t.getStackTrace();
Log.e(TAG, t.toString());
Utils.showToastMessage(getActivity(), "Error retrieving Incidents", true);
}
});
}
private class updateIncidentTemplateTask extends AsyncTask<EntityCategories, Void, Boolean> {
#Override
protected Boolean doInBackground(EntityCategories... params) {
updateIncidents(params[0]);
return true;
}
protected void onPostExecute(Boolean b) {
getLocalIncidentTemplate();
spinner.setVisibility(View.GONE);
}
}
Here is the Database Update Using ORMlite:
private void updateIncident(EntityCategories categories) {
try {
categories.setId("MobilePlan");
//Update base categories
GenericDAO.getInstance(EntityCategories.class).addOrUpdate(categories);
for (EntityCategories.EntityCategory currentCategory : new ArrayList<>(categories.getCategories())) {
if (currentCategory.getmPlans() != null) {
for (EntityPlan myPlan : new ArrayList<>(currentCategory.getmPlans())) {
EntityPlan oldPlan = GenericDAO.getInstance(EntityPlan.class).queryById(String.valueOf(myPlan.getmId()));
myPlan.setCategories(currentCategory);
if (oldPlan != null) {
if (!myPlan.getmDateModification().equals(oldPlan.getmDateModification())) {
GenericDAO.getInstance(EntityPlan.class).addOrUpdate(myPlan);
}
} else {
GenericDAO.getInstance(EntityPlan.class).addOrUpdate(myPlan);
}
}
} else {
continue;
}
GenericDAO.getInstance(EntityLabel.class).addOrUpdate(currentCategory.getmLabel());
currentCategory.setCategories(categories);
GenericDAO.getInstance(EntityCategories.EntityCategory.class).addOrUpdate(currentCategory);
}
} catch (Exception e) {
e.printStackTrace();
}
Log.d(TAG, "DATA updated");
}
For your particular case, you should use the AsyncTask to retrieve data from the backend and place it in the database.
Remember that AsyncTask has three main methods:
onPreExecute() that runs on the UI thread. Useful when you need to prep something that requires UI thread (touching views and whatnot)
doInBackGround() this runs on background thread
onPostExecute() runs also on the UI thread.
In onPostExecute() you could notify your adapter of the new data.
If I were you, I'd use loaders to get notified and retrieve the data off the database. So that the complete chain would be some:
AsyncTask pulls data from the backend and stores it in the database
Your loader will get notified that something changed inside the database and will pull the data from it and call onLoadFinished() method inside your activity/fragment
onLoadFinished() passes the data to the view adapter.
I haven't gone into detail as to how to implement this. I just presented the overall architecture.
I have trouble understanding what run on the UI thread and what doesn't and should be started as his own thread
The short answer:
Everything that might block the UI thread (in other words, might take time) should run on a worker thread (threadpool or dedicated)
DB actions and network requests are classic examples for actions that should always run asynchronously.
In your case I would just use an ORM to wrap all the interaction with the DB, such as ORMlite or any other you find more suitable, in that case you will not have to concern yourself with the inner workings and just provide callbacks for when your calls have finished (successfully or not)
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 have an acitivty which looks for user location (MyLocation class), then with the geopoint or without it it runs an AsyncTask to connect to server and get list of cities from my server. When the list is ready it saves them in ArrayList cities. Once the cities ArrayList is filled I would like it to be saved for good (proof to configuration changes). CityItem implements Parcelable. I save them in onSaveInstanceState and retrieve them onCreate.
Now, everything works fine if the task has completed and the cities list is filled. Then I rotate my device back and forth and Log.i("StartActivity", "Cities list downloaded:"+cities.toString()); gets called.
But if I rotate the device before the geopoint was found (or the task finished - hard to tell because it happens fast), then
public void gotCities(ArrayList<CityItem> _cities){
cities = _cities;
Log.i("StartActivity", "gotCities("+cities.size()+"): "+cities.toString());
}
gets called (and cities are perfectly fine in the log) but when I rotate it once more ArrayList cities appears to be null again.
It appears that if the configuration changed and the savedInstanceState.cities was null, the ArrayList cities is somehow created again and it's not the same ArrayList as the one in gotCities() function.
I'm pretty sure it's something easy but I've been searching for answer for hours and I simply can't do it.
Code of the Activity:
public class StartActivity extends Activity {
public static final String PREFS_NAME = "PrefsFile";
MyLocation myLocationObject = null;
LatLngPoint point = null;
ArrayList<CityItem> cities = null;
FindCityTask task = null;
Activity startActivity;
#Override
public void onCreate(Bundle savedInstanceState) {
if(savedInstanceState!=null) if(savedInstanceState.containsKey("cities")) cities = savedInstanceState.getParcelableArrayList("cities"); if(cities!=null) Log.i("Cities retrieved", cities.toString());
super.onCreate(savedInstanceState);
startActivity = this;
setContentView(R.layout.start);
//check if the configuration (orientation) has been changed
NonConfigurationObject nco = (NonConfigurationObject)getLastNonConfigurationInstance();
if(nco!=null) if(nco.myLocationObject!=null) myLocationObject = nco.myLocationObject;
if(nco!=null) if(nco.task!=null) task = nco.task;
if(cities==null){
Log.i("StartActivity", "Cities list is empty - retrieve them.");
if(myLocationObject==null){
getGeopoint();
}
} else {
Log.i("StartActivity", "Cities list downloaded:"+cities.toString());
}
}
private void getGeopoint(){
if(isOnline()){ //there is internet connection
if(myLocationObject==null){
myLocationObject = new MyLocation();
//calls function to check user location (returns false if no providers are enabled
if(!myLocationObject.getLocation(this, locationResult)){ /*TODO handle */Log.i("StartActivity", "Location providers disabled");}
}
} else { //not online - show msg
Log.i("StartActivity", "No internet connection");
}
}
//waits for user geopoint. then starts FindCityTask
LocationResult locationResult = new LocationResult(){
#Override
public void gotLocation(final Location location){
if(location!=null){
// location found
Log.i("StartActivity", "Received location: "+location.toString());
point = new LatLngPoint((float)location.getLatitude(), (float)location.getLongitude());
} else {
// location not found
Log.i("StartActivity", "No location received after 20 seconds");
point = null;
}
//RUN TASK to connect to server to get cities list (even if there's no geopoint)
task = new FindCityTask(startActivity);
task.execute(point);
}
};
public void gotCities(ArrayList<CityItem> _cities){
cities = _cities;
Log.i("StartActivity", "gotCities("+cities.size()+"): "+cities.toString());
}
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
Log.i("onSaveInstanceState", "onSaveInstanceState");
if(cities!=null) savedInstanceState.putParcelableArrayList("cities", cities);
}
#Override
public NonConfigurationObject onRetainNonConfigurationInstance() {
NonConfigurationObject nco = new NonConfigurationObject();
if(myLocationObject!=null){
nco.myLocationObject = myLocationObject;
}
if(task!=null){
nco.task = task;
}
return nco;
}
static class NonConfigurationObject{
MyLocation myLocationObject;
FindCityTask task;
}
gotCities() method is called from AsyncTask onPostExecute:
#Override
protected void onPostExecute(Void result) {
if(this.activity!=null){
((StartActivity) activity).gotCities(cities);
}
}
When orientation cnange hits, your activity is stopped, destroyed and created anew, and the only callback which is guaranted to be called is onPause(). The same happens when screen lock kick in - it forces your activity in portrait mode.
I recommend you to read about android activity lifecycle. Rule of thumb is:
onCreate() is for initalizing interface objects and services
onResume() is called when your activity comes on top and is about to be presented to user
onPause() when it loses focus and is not presented to use anymore.
Since obtaining location takes long time, it is better to move it away from activity to background service, ( start it in onCreate() if necessary ) and decouple its lifecycle from activity. Service can pass results to activity via broadcast messages, or java method calls
And look into your onSaveInstanceState() - first you are calling super.onSaveInstanceState(), and then you modify bundle to include your data. This way they are never saved.
I finally got it. It's the AsyncTask which pointed to wrong (the one before configuration change) activity. The trick is to attach to the task the reference to activity (and to do it in the right moment).
So the first thing to do is to put these functions in the AsyncTask:
void attach(Activity activity){
this.activity = activity;
}
void detach(){
this.activity = null;
}
When the task is first called we should attach the activity to it.
Then onRetainNonConfigurationInstance() detach it.
#Override
public NonConfigurationObject onRetainNonConfigurationInstance() {
//normally it would return only the task, but i have to return another object
//hence the NonConfigurationObject which holds reference to both the AsyncTask and MyLocation
//(as seen in the original question)
NonConfigurationObject nco = new NonConfigurationObject();
if(task!=null){
task.detach();
nco.task = task;
}
return nco;
}
And finally attach it again when we call getLastNonConfigurationInstance() in onCreate().
//check if the configuration (orientation) has been changed
NonConfigurationObject nco = (NonConfigurationObject)getLastNonConfigurationInstance();
if(nco!=null){ //not created for the first time
Log.i("StartActivity", "NCO: "+nco.toString());
task = nco.task;
if(task!=null){ //nco can be present but task still null
task.attach(this);
} else {
task = new FindCityTask(this);
}
} else {
Log.i("StartActivity", "NCO: null");
task = new FindCityTask(this);
}
Please share your thoughts on this solution if you have any. I'll modify the question so it better suits the problem.
I'm running an activity, which has to download a fairly large image from the Internet, and then display it. This works: the download is done via an AsyncTask, and a progress dialog is shown. And when the download is finished, the activity showing the image is called.
The problem I have is that the real work is done in an external class. This as other activities call the same routine to fetch an image. So I can not immediately call postUpdate() to set the update as this call would be done in another class. Now I wonder how I can get the progress updates back to my AsyncTask so my progress dialog can show the actual progress made
The AsyncTask subclass currently looks like this:
private class StartShowImage extends AsyncTask<String, Integer, String> {
private ProgressDialog dialog;
#Override
protected void onPreExecute() {
// Toon een dialog met draaiend wiel terwijl we de foto
// aan het ophalen zijn.
dialog = new ProgressDialog(ShowDeaddrop.this);
dialog.setTitle(R.string.progress_dialog_title);
dialog.setMessage(getResources().getString(
R.string.progress_dialog_fetching_image));
dialog.show();
}
/**
* De hoofdroutine; haalt de foto op.
*/
#Override
protected String doInBackground(final String... params) {
final String imageName = params[0];
String result = null;
try {
result = DeaddropUtil.getImage(context, imageName, ""
+ deaddropID, true);
} catch (final IOException e) {
Log.v(TAG, "Failed to download image " + imageName);
Log.v(TAG, "" + e);
}
return result;
}
/**
* Als we de foto hebben, start ShowImage.
*/
#Override
protected void onPostExecute(final String imageName) {
dialog.dismiss();
if (isActive)
if (imageName == null)
Toast.makeText(context, R.string.toast_show_image_failed,
Toast.LENGTH_SHORT).show();
else {
final Intent i = new Intent(ShowDeaddrop.this,
ShowImage.class);
i.putExtra("imageName", imageName);
startActivityForResult(i, SHOW_IMAGE);
}
}
}
isActive is a boolean that keeps track of whether this activity is active - it's set in onCreate and onResume and unset in onPause.
I've been looking into a broadcast intent - I've seen an example on how to send back such an intent to the main activity, but the problem is that the listener has to be registered/unregstered in onResume/onPause - and AsyncTask is a separate thread. So it seems this method can not be used safely that way.
Edit restating the question, hoping to get answers that address my question.
AsyncTask sets up progress dialog.
The onExecute() thread can directly update the progress dialog. No problem there.
The actual work is done in an external class, so the progress information is known by that external class, which has to communicate it back to the AsyncTask one way or another.
AsyncTask will have to have some kind of listener, or handler, or something that the external class can call back to, in order to give progress updates.
The question is: how to perform this last part of the process? What is a suitable listener? How to implement such a listener? Which thread does the listener end up in - the UI thread like .onPreExecute() and .onPostExecute(), or the work thread from .doInBackground()?
Solved by using a BroadcastIntent.
Main activity creates BroadcastReceiver, IntentFilter and progress dialog; AsyncTask registers/unregisters this receiver and shows/dismisses the dialog in onPreExecute/onPostExecute respectively.
With extra bits in onResume and onPause to not have active receivers when the activity itself is inactive.
It took me a while to understand this intent broadcast, but after that it was a very quick and easy implementation.
If I understand correctly you are wanting to update the progress bar from the async task. I would probably look at the onProgressUpdate of async task. http://developer.android.com/reference/android/os/AsyncTask.html
Your class StartShowImage doesn't have member. This object you have used to create toast also. if you make member Activity and construction declaration for it you can initialize it by constructor like this before you call it's public method which you want to do :
private Activity context;
public StartShowImage(Activity context){
this.context=context;
}
StartShowImage object=new StartShowImage(this)
object.postUpdate();
This should work and your calling class should extent Activity