Fragment loses Activity while networking (android) - android

I initiate most of my networking calls from Fragments and then use callbacks to tell the Fragment whether or not the networking task succeeded or failed and to update ui accordingly.
On rare occassions (.25% of sessions) my program is crashing with a null-pointer exception due to getActivity() returning null when the code in my callback runs. I know that I can use a null check on getActivity() to prevent this from happening, however what's the best practice for handling this issue?
The null check seems to be little more than a crash prevention tool as the program still needs the data from the networking task.
The code looks something like the following:
private void queryServer() {
// networking task should query server for user id, if successful store it
// in user preferences to be accessed by fragment in callback
new networkingTask(new VolleyCallback() {
#Override
public void onSuccess() {
// code below needs null check on getActivity - but what else?
mUserId = new UserPreferences(getActivity()).getUserId();
}
#Override
public void onFail() {
// booooo
}
});
}

As I stated in my comment above, what is likely happening is the Activity/Fragment pair are being stopped or destroyed by the system. This will happen for a variety of reasons, such as a screen orientation change. Because your handler is a method on the fragment object, you are working with a "dead" fragment by the time the call returns. There are several patterns for dealing with this. In short you need to make your handler aware of the current fragment, and you can accomplish this by using lifecycle methods.
Below is an example of a pattern you could use. I tried to make the example as minimal as possible.
import android.app.Activity;
import android.app.Fragment;
public class MyFragment extends Fragment {
// This is static so that it will not go out of scope when the original
// fragment is destroy. This allows it to be access from all MyFragment
// instances.
static MyResponseProcessor processor = new MyResponseProcessor();
// This will be the class that handles your network call.
public static class MyResponseProcessor {
// This instance variable is alway a reference to the currently displayed fragment.
private Fragment activeFragement;
public void setActiveFragement(Fragment activeFragement) {
this.activeFragement = activeFragement;
}
// This method, which is for demonstration purposes, shows how you would handle a network response.
public void handleResponse(SomeResponseObject) {
if (activeFragement != null) {
// Now you can get the activity
Activity activity = activeFragement.getActivity();
} else {
// Yes it is possible that there is no active fragment.
// If the user has stayed on the same screen, then the
// fragment of interest will likely be re-created, and
// this window of time with no fragment will be brief.
//
// Note that this null-check is very different than the
// null-check you describe. In your case the reference is
// guaranteed to be null forever. In this case, the reference
// will eventually become non-null.
}
}
}
#Override
public void onStart() {
super.onStart();
// At this point in the fragment lifecycle, the fragment is both running and is attached to an Activity.
// Thus "getActivity" calls are safe from this point onward.
processor.setActiveFragement(this);
}
#Override
public void onStop() {
super.onStop();
// At this point in the fragment lifecycle, the fragment has been stopped and is about to lose its connection to the activity.
// So after this point, calls to "getActivity" are probably not safe.
// DISCLAIMER - I have not tested this. You might want to do this in a
// different method such as "onDestroyView()"
processor.setActiveFragement(null);
}
}

Related

Android: questions about manually handling configuration changes while thread runs

I am trying to update my app to handle configuration changes (especially screen turning) manually.
I have some questions about what happens when changes happen during a Thread execution.
I have created an abstract class I call ThreadTask which uses Threads and Handlers to the main thread's looper to send updates to the main thread. This is my implementation of AsyncTask but with threads, I prefer this to using AsyncTask because I have more control over it.
It also has two methods to register an observer to the above events, it uses this interface:
public interface TaskObserver {
void pre_execute();
void on_progress(ProgressData progress);
void finished(Object result);
void cancelled(Object result);
}
The abstract members that the subclass must implement are :
abstract Object do_in_background();
and some concrete members are:
synchronized void add_observer(TaskObserver observer){
_observers.add(observer);
}
synchronized void remove_observer(TaskObserver observer){
_observers.remove(observer);
}
synchronized void post_execute(Object result) {
int observers = _observers.size();
for (int idx = 0; idx < observers; idx++) {
_observers.get(idx).finished(result);
}
}
///plus all the other methods of the interface
So when I implement a concrete class it would go something like this:
public class MyThreadTask extends ThreadTask{
#Override
Object do_in_background() {
progress.primary_max = 5;
for (int cur = 0 ; cur < 5 ; cur++) {
progress.primary = cur;
publish();
Thread.Sleep(3000);
}
}
}
and I updated the activity that calls this like so:
static final string TAG ="my_main_activity";
MyDataFragment _data; //this is a datafragment, a fragment with retaininstancestate , and a public `MyThreadTask task` variable
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (_data == null) {
_data = (MyDataFragment)getFragmentManager().findFragmentByTag(TAG + "_data");
if (_data == null) {
_data = new MyDataFragment();
getFragmentManager().beginTransaction().add(_data, TAG + "_data").commit();
}
}
if (_data.task != null) {
_data.task.register(this);
}
}
#Override
protected void onDestroy() {
super.onDestroy();
if (_data.task != null) {
_data.task.remove(this);
}
}
this makes sure that I always have a reference to the correct thread
When I wish to start the task I do it like so:
button.setOnClickListener((v) -> {
if (_data.task != null) return; //to make sure only one thread starts at a time
_data.task = new MyThreadTask();
_data.task.register(this);
_data.task.start(); //this is not thread's start, it is mine, it does other things too
})
and when the thread finishes it calls void finished(Object result) which I handle like so:
void finished(Object result) {
try {
//get result;
} finally {
_data.task.remove(this);
_data.task = null;
}
}
here are my questions:
a) is declaring my observer methods as synchronized necessary? I did it just to make sure , but when the activity is destroyed and then recreated, does it happen on the same thread? is there a chance for example that a progress_update may happen while an observer is being removed during onDestroy?
b) what will happen if a thread finishes and calls post_execute(which is important) during a configuration change? will the update be lost?
c) If indeed the update is lost because it currently has no observers, is there a way, either in my implementation or a different one, to handle the above?
Thanks in advance for any help you can provide
The preferred way to keep a background task alive through a configuration change is by hosting it in a retained fragment. The same instance of the fragment will persist through the configuration change. When the fragment is paused, check the activity's isChangingConfigurations and cancel the task only if it's false.
I don't know if this is documented anywhere, but it seems that the entire configuration change is posted to the main thread so that nothing else can run between pausing the old activity and resuming the new one. If you were using an AsyncTask in a retained fragment, you would be assured that its onPostExecute could not run during the configuration change. With your approach, the task could easily complete when there is no observer.
Asynctask does not handle configuration changes that well. I think, instead of Asynctask you should use AsynctaskLoader which can handle the config changes easily and it behaves within the life cycle of activities/fragments.
When you run AsyncTask and if the android system kills your activity/fragment(during config changes or memory conservation) then your doInBackground() method still keeps on running in the background and this can lead to undesirable results.
Therefore, instead of using AsyncTask you can use AsynctaskLoader or if you are populating data from SQLite then you can use CursorLoader.
References:
Guideline to choose among AsyncTaskLoader and AsyncTask to be used in Fragment
https://developer.android.com/reference/android/content/AsyncTaskLoader.html

OnSaveInstanceState/ RestCalls

I am relatively new to Android development, and I have a question about onSaveInstanceState(). I am currently working on a login Activity for an app. To check to see if the user can login to their account, I perform a rest call to a server and, based on the response-code, see if I should grant access to the user. The root of my question is based on the fact that I am trying to avoid passing the Activity's Context to my rest-call class. To do this, I create a boolean field in my login Activity representing whether or not the rest-call was successful and a runnable that updates said boolean that I pass to the rest-call class. I know this goes against the idea of an AsyncTask, but I can't find any alternative to simply putting up a dialog box telling the user to wait while this happens. My questions are below.
1) If I use savedInstanceState() in the onCreate method, how do I instantiate this boolean field for the first time barring null checking an Object boolean? What I mean by this is that after the Activity is destroyed for whatever reason (such as orientation change, etc...) I will use the boolean value stored in my overriden onSaveInstanceState method; however, when it is created for the first time, it has no reference to a boolean value so it has to create one.
2) Does this Runnable even help? I did it so that I wouldn't have to pass the context, but if the Activity is going to be deleted before the RestCall(AsyncTask) is complete, does it really matter whether you pass the context or a Runnable affecting a field of the Activity? The more I think about this, the more I believe it is not going to make much of a difference as it will still result in it pointing to a non-existent object. I am trying to avoid using a Singleton design as I have gathered it is not optimal, but because of the potential lag in time with an AsyncTask, I am beginning to think that it may not be avoidable.
I know onSaveInstanceState() is a topic that has been brought up a lot on StackOverflow, however, I could not find an answer to these questions. I apologize if there has already been a thread for this, but any help or guidance on this would be greatly appreciated! Thank You!
Login Activities' setup:
public class LoginActivity extends Activity implements View.OnClickListener {
private EditText username_et;
private EditText password_et;
private Button login_b;
private boolean login_success = true;
private Runnable run;
/**
* Instances created when app starts
*/
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.view_login);
// login_success = false;
login_success = savedInstanceState.getBoolean("login_success");
username_et = (EditText) findViewById(R.id.username_text);
username_et.setOnClickListener(LoginActivity.this);
password_et = (EditText) findViewById(R.id.password_text);
password_et.setOnClickListener(LoginActivity.this);
login_b = (Button) findViewById(R.id.login_button);
login_b.setOnClickListener(LoginActivity.this);
run = new Runnable() {
#Override
public void run() {
login_success = true;
}
};
}
#Override
public void onSaveInstanceState(Bundle savedInstanceState){
super.onSaveInstanceState(savedInstanceState);
savedInstanceState.putBoolean("login_success", login_success);
}
Congratulations. You just discovered Android's dirty little secret.
AsyncTask has an inherent design flaw. It doesn't deal well with configuration changes that happen during background task execution because of exactly the problem you mentioned. It needs to hold a reference to the activity, but there's no guarantee that the reference will still be valid by the time the background task completes.
Here are two ways to overcome this problem:
I refer you to Alex Lockwood's excellent blog post on using hidden fragments with setRetainInstance(true) to span activity destruction and recreation. This is a more involved solution than the next one, but this solution has the advantage that you can still report progress with callbacks. If you were intending to call publishProgress() in your AsyncTask, then this is the method you should use.
Use a Loader. Loaders were designed around database data retrieval in the background, but the fact is that they can also be used to handle remote server access in the background as well. I use a Loader for the majority of my remote server tasks.
Here's an example:
public static class ResetPasswordLoader extends AsyncTaskLoader<Pair<CharSequence, Exception>> {
private static final String TAG = "ResetPasswordLoader ";
private String mEmail;
public ResetPasswordLoader(Context context, String email) {
super(context);
mEmail = email;
// set the content-changed flag
onContentChanged();
}
#Override
protected void onStartLoading() {
// only start the load if the content-changed flag is set
// takeContentChanged() returns the value of the flag before it is cleared
if (takeContentChanged()) {
forceLoad();
}
}
#Override
public Pair<CharSequence, Exception> loadInBackground() {
CharSequence result = null;
Exception exc = null;
try {
result = Service.getInstance().resetPassword(mEmail);
} catch (RemoteServiceException e) {
exc = e;
Log.e(TAG, "loadInBackground(), email = " + mEmail, e);
}
return new Pair<>(result, exc);
}
}
Also, in my onLoadFinished() override I make sure to call loaderManager.destroyLoader() on the loader's id.
Again, Alex Lockwood's blog has some great articles on loaders as well.
For the UI, something I do frequently is put up a indeterminate progress bar over the UI upon calling loaderManager.initLoader(). I also set a boolean like mProgressShown. This boolean gets saved in onSaveInstanceState, so when the activity/fragment is created again, I restore the boolean value which tells me to show the progress bar immediately. Some time later onLoadFinished will be called and I clear mProgressShown and hide the progress bar.

Prevent Thread from being garbage collected, and prevent context leak

I want to customize the process of obtaining the authentication token from AccountManager.
AccountManager has getAuthToken() and getAuthTokenByFeatures() methods, but I want to implement a customized flow, which includes switching between activities, etc...
I wanted to implement it the following way:
public AccountManagerFuture<Bundle> getAuthTokenForActiveAccount() {
GetAuthTokenForActiveAccountFuture future =
new GetAuthTokenForActiveAccountFuture(MyActivity.this);
future.start();
return future;
}
Using the following nested class in my activity:
private static class GetAuthTokenForActiveAccountFuture extends Thread implements
AccountManagerFuture<Bundle> {
private final Activity mActivity;
public GetAuthTokenForActiveAccountFuture(Activity activity) {
mActivity = activity;
// TODO: write this method
}
#Override
public void run() {
// TODO: write this method
}
#Override
public boolean cancel(boolean b) {
// TODO: write this method
return false;
}
#Override
public boolean isCancelled() {
// TODO: write this method
return false;
}
#Override
public boolean isDone() {
// TODO: write this method
return false;
}
#Override
public Bundle getResult() throws
OperationCanceledException, IOException, AuthenticatorException {
return internalGetResult(null, null);
}
#Override
public Bundle getResult(long timeout, TimeUnit timeUnit) throws
OperationCanceledException, IOException, AuthenticatorException {
return internalGetResult(timeout, timeUnit);
}
private Bundle internalGetResult(Long timeout, TimeUnit timeUnit) throws
OperationCanceledException, IOException, AuthenticatorException {
// TODO: write this method
return null;
}
}
My idea was that I could create my own AccountManagerFuture object and "unblock" its getResult() method only after all the required steps were done (some of them include activity switching).
I got two issues here:
I need Activity context for switching to other activities when necessary, but the Activity I pass into constructor should be destroyed when I switch to other activity, but it won't because my Thread holds a reference to it... So I create a memory leak here. It seems that making the inner class non-static won't resolve this issue - the reference returned from getAuthTokenForActiveAccount() will still prevent from the outer Activity to be garbage collected. Is there any way I could achieve what I try to do without leaking the context?
Thread is eligible for garbage collection once its run() method returns, right? But in my case I want this thread to stick around because it also functions as AccountManagerFuture - it should be kept in memory until all references to it are gone. My question is this: is it enough to keep a (strong) reference to Thread for preventing it from being garbage collected? If not, how could I force this Thread to stick around until all references are gone?
At first. Making your Future non-static would make it having an implicit reference to its outer class - the Activity.
You should used some form of indirect communication between your future and your Activities..You should probably move it into Service anyway - did you think about any configuration change? Where do you hold the reference for your Future?
I would advice you to either move your flow into fragments - then you wouldn't have to switch Activities - and place your future into a retained Fragment (to make it survive orientation change) or move it into a background service and communicate with your activities (or any sort of UI) through broadcastreceivers or event bus.
Thread won't be garbage collected as long as you keep some reference to it. No matter if its finished or not. I think that you are confusing this with the fact that a running Thread won't be garbage collected even without keeping references to it. (I guess tha JVM does so, but I have to admit I'm not sure about this)
issue 1 solution:
use private WeakReference mContextHolder. when you need context - call mContextHolder.get() and check on null;
issue 2 solution:
Use Service which will host your threads.

Best practice to implement Retrofit callback to recreated activity?

I'm switching to Retrofit and trying to understand proper architecture for using it with async callbacks.
For example I have an interface:
interface RESTService{
#GET("/api/getusername")
void getUserName(#Query("user_id") String userId,
Callback<Response> callback);
}
And I run this from main activity:
RestAdapter restAdapter = new RestAdapter.Builder()
.setServer("WEBSITE_URL")
.build();
RESTService api = restAdapter.create(RESTService.class);
api.getUserName(userId, new Callback<Response> {...});
Then user rotates the device and I have newly created activity... What was happen here? How can I get response to the new activity (I assume that api call in background will execute longer than first activity life). Maybe I must use static instance of callback or what? Please show me the right way...
Use otto.
There are a lot of samples to mix otto and retrofit, for example https://github.com/pat-dalberg/ImageNom/blob/master/src/com/dalberg/android/imagenom/async/FlickrClient.java
Or read this post http://www.mdswanson.com/blog/2014/04/07/durable-android-rest-clients.html
It answers on almost all questions
For potential long running server calls i use an AsyncTaskLoader. For me, the main advantage of Loaders are the activity-lifecycle handling. onLoadFinished is only called if your activity is visible to the user. Loaders are also shared between activity/fragment and orientation changes.
So i created an ApiLoader which uses retrofits synchronous calls in loadInBackground.
abstract public class ApiLoader<Type> extends AsyncTaskLoader<ApiResponse<Type>> {
protected ApiService service;
protected ApiResponse<Type> response;
public ApiLoader(Context context) {
super(context);
Vibes app = (Vibes) context.getApplicationContext();
service = app.getApiService();
}
#Override
public ApiResponse<Type> loadInBackground() {
ApiResponse<Type> localResponse = new ApiResponse<Type>();
try {
localResponse.setResult(callServerInBackground(service));
} catch(Exception e) {
localResponse.setError(e);
}
response = localResponse;
return response;
}
#Override
protected void onStartLoading() {
super.onStartLoading();
if(response != null) {
deliverResult(response);
}
if(takeContentChanged() || response == null) {
forceLoad();
}
}
#Override
protected void onReset() {
super.onReset();
response = null;
}
abstract protected Type callServerInBackground(SecondLevelApiService api) throws Exception;
}
In your activity you init this loader like this:
getSupportLoaderManager().initLoader(1, null, new LoaderManager.LoaderCallbacks<ApiResponse<DAO>>() {
#Override
public Loader<ApiResponse<DAO>> onCreateLoader(int id, Bundle args) {
spbProgress.setVisibility(View.VISIBLE);
return new ApiLoader<DAO>(getApplicationContext()) {
#Override
protected DAO callServerInBackground(ApiService api) throws Exception {
return api.requestDAO();
}
};
}
#Override
public void onLoadFinished(Loader<ApiResponse<DAO>> loader, ApiResponse<DAO> data) {
if (!data.hasError()) {
DAO dao = data.getResult();
//handle data
} else {
Exception error = data.getError();
//handle error
}
}
#Override
public void onLoaderReset(Loader<ApiResponse<DAO>> loader) {}
});
If you want to request data multiple times use restartLoader instead of initLoader.
I've been using a kind of MVP (ModelViewPresenter) implementation on my Android apps. For the Retrofit request I made the Activity calls it's respective Presenter, which in turn makes the Retrofit Request and as a parameter I send a Callback with a custom Listener attached to it (implemented by the presenter). When the Callback reach onSuccess or onFailure methods I call the Listener's respective methods, which calls the Presenter and then the Activity methods :P
Now in case the screen is turned, when my Activity is re-created it attaches itself to the Presenter. This is made using a custom implementation of Android's Application, where it keeps the presenters' instance, and using a map for recovering the correct presenter according to the Activity's class.
I don't know if it's the best way, perhaps #pareshgoel answer is better, but it has been working for me.
Examples:
public abstract interface RequestListener<T> {
void onSuccess(T response);
void onFailure(RetrofitError error);
}
...
public class RequestCallback<T> implements Callback<T> {
protected RequestListener<T> listener;
public RequestCallback(RequestListener<T> listener){
this.listener = listener;
}
#Override
public void failure(RetrofitError arg0){
this.listener.onFailure(arg0);
}
#Override
public void success(T arg0, Response arg1){
this.listener.onSuccess(arg0);
}
}
Implement the listener somewhere on the presenter, and on the overrode methods call a presenter's method that will make the call to the Activity. And call wherever you want on the presenter to init everything :P
Request rsqt = restAdapter.create(Request.class);
rsqt.get(new RequestCallback<YourExpectedObject>(listener));
Firstly, your activity leaks here because this line:
api.getUserName(userId, new Callback {...})
creates an anonymous Callback class that holds a strong reference to you MainActivity. When the device is rotated before the Callback is called, then the MainActivity will not be garbage collected. Depending on what you do in the Callback.call(), your app may yield undefined behaviour.
The general idea to handle such scenarios is:
Never create a non-static inner class (or an anonymous class as mentioned in the problem).
Instead create a static class that holds a WeakReference<> to the Activity/Fragment.
The above just prevents Leaks. It still does not help you get the Retrofit call back to your Activity.
Now, to get the results back to your component (Activity in your case) even after configuration change, you may want to use a headless retained fragment attached to your Activity, which makes the call to Retrofit. Read more here about Retained fragment - http://developer.android.com/reference/android/app/Fragment.html#setRetainInstance(boolean)
The general idea is that the Fragment automatically attaches itself to the Activity on configuration change.
I highly recommend you watch this video given at Google I/O.
It talks about how to create REST requests by delegating them to a service (which is almost never killed). When the request is completed it is immediately stored into Android's built-in database so the data is immediately available when your Activity is ready.
With this approach, you never have to worry about the lifecycle of the activity and your requests are handled in a much more decoupled way.
The video doesn't specifically talk about retrofit, but you can easily adapt retrofit for this paradigm.
Use Robospice
All components in your app which require data, register with the spice service. The service takes care of sending your request to the server (via retrofit if you want). When the response comes back, all components which registered get notified. If there is one of them not available any more (like an activity which got kicked because of rotation), it's just not notified.
Benefit: One single request which does not get lost, no matter whether you rotate your device, open new dialogs/fragments etc...
Using Retrofit2 to handle orientation change. I was asked this in a job interview and was rejected for not knowing it at the time but here it is now.
public class TestActivity extends AppCompatActivity {
Call<Object> mCall;
#Override
public void onDestroy() {
super.onDestroy();
if (mCall != null) {
if (mCall.isExecuted()) {
//An attempt will be made to cancel in-flight calls, and
// if the call has not yet been executed it never will be.
mCall.cancel();
}
}
}
}

Background task, progress dialog, orientation change - is there any 100% working solution?

I download some data from internet in background thread (I use AsyncTask) and display a progress dialog while downloading. Orientation changes, Activity is restarted and then my AsyncTask is completed - I want to dismiss the progess dialog and start a new Activity. But calling dismissDialog sometimes throws an exception (probably because the Activity was destroyed and new Activity hasn't been started yet).
What is the best way to handle this kind of problem (updating UI from background thread that works even if user changes orientation)? Did someone from Google provide some "official solution"?
Step #1: Make your AsyncTask a static nested class, or an entirely separate class, just not an inner (non-static nested) class.
Step #2: Have the AsyncTask hold onto the Activity via a data member, set via the constructor and a setter.
Step #3: When creating the AsyncTask, supply the current Activity to the constructor.
Step #4: In onRetainNonConfigurationInstance(), return the AsyncTask, after detaching it from the original, now-going-away activity.
Step #5: In onCreate(), if getLastNonConfigurationInstance() is not null, cast it to your AsyncTask class and call your setter to associate your new activity with the task.
Step #6: Do not refer to the activity data member from doInBackground().
If you follow the above recipe, it will all work. onProgressUpdate() and onPostExecute() are suspended between the start of onRetainNonConfigurationInstance() and the end of the subsequent onCreate().
Here is a sample project demonstrating the technique.
Another approach is to ditch the AsyncTask and move your work into an IntentService. This is particularly useful if the work to be done may be long and should go on regardless of what the user does in terms of activities (e.g., downloading a large file). You can use an ordered broadcast Intent to either have the activity respond to the work being done (if it is still in the foreground) or raise a Notification to let the user know if the work has been done. Here is a blog post with more on this pattern.
The accepted answer was very helpful, but it doesn't have a progress dialog.
Fortunately for you, reader, I have created an extremely comprehensive and working example of an AsyncTask with a progress dialog!
Rotation works, and the dialog survives.
You can cancel the task and dialog by pressing the back button (if you want this behaviour).
It uses fragments.
The layout of the fragment underneath the activity changes properly when the device rotates.
I've toiled for a week to find a solution to this dilemma without resorting to editing the manifest file. The assumptions for this solution are:
You always need to use a progress dialog
Only one task is performed at a time
You need the task to persist when the phone is rotated and the progress dialog to be automatically dismisses.
Implementation
You will need to copy the two files found at the bottom of this post into your workspace. Just make sure that:
All your Activitys should extend BaseActivity
In onCreate(), super.onCreate() should be called after you initialize any members that need to be accessed by your ASyncTasks. Also, override getContentViewId() to provide the form layout id.
Override onCreateDialog() like usual to create dialogs managed by the activity.
See code below for a sample static inner class to make your AsyncTasks. You can store your result in mResult to access later.
final static class MyTask extends SuperAsyncTask<Void, Void, Void> {
public OpenDatabaseTask(BaseActivity activity) {
super(activity, MY_DIALOG_ID); // change your dialog ID here...
// and your dialog will be managed automatically!
}
#Override
protected Void doInBackground(Void... params) {
// your task code
return null;
}
#Override
public boolean onAfterExecute() {
// your after execute code
}
}
And finally, to launch your new task:
mCurrentTask = new MyTask(this);
((MyTask) mCurrentTask).execute();
That's it! I hope this robust solution will help someone.
BaseActivity.java (organize imports yourself)
protected abstract int getContentViewId();
public abstract class BaseActivity extends Activity {
protected SuperAsyncTask<?, ?, ?> mCurrentTask;
public HashMap<Integer, Boolean> mDialogMap = new HashMap<Integer, Boolean>();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getContentViewId());
mCurrentTask = (SuperAsyncTask<?, ?, ?>) getLastNonConfigurationInstance();
if (mCurrentTask != null) {
mCurrentTask.attach(this);
if (mDialogMap.get((Integer) mCurrentTask.dialogId) != null
&& mDialogMap.get((Integer) mCurrentTask.dialogId)) {
mCurrentTask.postExecution();
}
}
}
#Override
protected void onPrepareDialog(int id, Dialog dialog) {
super.onPrepareDialog(id, dialog);
mDialogMap.put(id, true);
}
#Override
public Object onRetainNonConfigurationInstance() {
if (mCurrentTask != null) {
mCurrentTask.detach();
if (mDialogMap.get((Integer) mCurrentTask.dialogId) != null
&& mDialogMap.get((Integer) mCurrentTask.dialogId)) {
return mCurrentTask;
}
}
return super.onRetainNonConfigurationInstance();
}
public void cleanupTask() {
if (mCurrentTask != null) {
mCurrentTask = null;
System.gc();
}
}
}
SuperAsyncTask.java
public abstract class SuperAsyncTask<Params, Progress, Result> extends AsyncTask<Params, Progress, Result> {
protected BaseActivity mActivity = null;
protected Result mResult;
public int dialogId = -1;
protected abstract void onAfterExecute();
public SuperAsyncTask(BaseActivity activity, int dialogId) {
super();
this.dialogId = dialogId;
attach(activity);
}
#Override
protected void onPreExecute() {
super.onPreExecute();
mActivity.showDialog(dialogId); // go polymorphism!
}
protected void onPostExecute(Result result) {
super.onPostExecute(result);
mResult = result;
if (mActivity != null &&
mActivity.mDialogMap.get((Integer) dialogId) != null
&& mActivity.mDialogMap.get((Integer) dialogId)) {
postExecution();
}
};
public void attach(BaseActivity activity) {
this.mActivity = activity;
}
public void detach() {
this.mActivity = null;
}
public synchronized boolean postExecution() {
Boolean dialogExists = mActivity.mDialogMap.get((Integer) dialogId);
if (dialogExists != null || dialogExists) {
onAfterExecute();
cleanUp();
}
public boolean cleanUp() {
mActivity.removeDialog(dialogId);
mActivity.mDialogMap.remove((Integer) dialogId);
mActivity.cleanupTask();
detach();
return true;
}
}
Did someone from Google provide some "official solution"?
Yes.
The solution is more of an application architecture proposal rather that just some code.
They proposed 3 design patterns that allows an application to work in-sync with a server, regardless of the application state (it will work even if the user finishes the app, the user changes screen, the app gets terminated, every other possible state where a background data operation could be interrumpted, this covers it)
The proposal is explained in the Android REST client applications speech during Google I/O 2010 by Virgil Dobjanschi. It is 1 hour long, but it is extremely worth watching.
The basis of it is abstracting network operations to a Service that works independently to any Activity in the application. If you're working with databases, the use of ContentResolver and Cursor would give you an out-of-the-box Observer pattern that is convenient to update UI without any aditional logic, once you updated your local database with the fetched remote data. Any other after-operation code would be run via a callback passed to the Service (I use a ResultReceiver subclass for this).
Anyway, my explanation is actually pretty vague, you should definititely watch the speech.
While Mark's (CommonsWare) answer does indeed work for orientation changes, it fails if the Activity is destroyed directly (like in the case of a phone call).
You can handle the orientation changes AND the rare destroyed Activity events by using an Application object to reference your ASyncTask.
There's an excellent explanation of the problem and the solution here:
Credit goes completely to Ryan for figuring this one out.
After 4 years Google solved the problem just calling setRetainInstance(true) in Activity onCreate. It will preserve your activity instance during device rotation. I have also a simple solution for older Android.
you should call all activity actions using activity handler. So if you are in some thread you should create a Runnable and posted using Activitie's Handler. Otherwise your app will crash sometimes with fatal exception.
This is my solution: https://github.com/Gotchamoh/Android-AsyncTask-ProgressDialog
Basically the steps are:
I use onSaveInstanceState to save the task if it is still
processing.
In onCreate I get the task if it was saved.
In onPause I discard the ProgressDialog if it is shown.
In onResume I show the ProgressDialog if the task is still
processing.

Categories

Resources