I have been trying to understand how to handle configuration changes when doing background tasks. From my research i found out that the best solution is to put the task in a headless fragment with setRetainInstance(true)
set. There has been lots of discussion on this topic everywhere, eg. here, and here, but i have not been able to get any of them to work for me.
What i am trying to implement is this:
- Have my AsyncTask in a Separate File (as an outer class).
- Start the AsyncTask from inside the Headless Fragment.
- Display a progress Dialog, instead of a progress bar.
- Return results from the AsyncTask to the parent Activity.
So i took this tutorial which does basically the same thing,on button click it runs some dummy task and displays progress with a progress bar. But has the AsyncTask as an inner class. I tried to move the Asynctask to an outer class, and the app just crashes when i try to start the background task.
Here is the code:
MainActivity (parent Activity)
public class MainActivity extends FragmentActivity implements TaskCallbacks {
private static final String TAG = MainActivity.class.getSimpleName();
private static final String KEY_CURRENT_PROGRESS = "current_progress";
private static final String KEY_PERCENT_PROGRESS = "percent_progress";
private TaskFragment mTaskFragment;
private ProgressBar mProgressBar;
private TextView mPercent;
private Button mButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
Log.i(TAG, "onCreate(Bundle)");
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Initialize views
mProgressBar = (ProgressBar) findViewById(R.id.progress_horizontal);
mPercent = (TextView) findViewById(R.id.percent_progress);
mButton = (Button) findViewById(R.id.task_button);
mButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (mTaskFragment.isRunning()) {
mTaskFragment.cancel();
} else {
mTaskFragment.start();
}
}
});
// Restore saved state
if (savedInstanceState != null) {
mProgressBar.setProgress(savedInstanceState.getInt(KEY_CURRENT_PROGRESS));
mPercent.setText(savedInstanceState.getString(KEY_PERCENT_PROGRESS));
}
FragmentManager fm = getSupportFragmentManager();
mTaskFragment = (TaskFragment) fm.findFragmentByTag("task");
// If the Fragment is non-null, then it is currently being
// retained across a configuration change.
if (mTaskFragment == null) {
mTaskFragment = new TaskFragment();
fm.beginTransaction().add(mTaskFragment, "task").commit();
}
if (mTaskFragment.isRunning()) {
mButton.setText(getString(R.string.cancel));
} else {
mButton.setText(getString(R.string.start));
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(KEY_CURRENT_PROGRESS, mProgressBar.getProgress());
outState.putString(KEY_PERCENT_PROGRESS, mPercent.getText().toString());
}
/****************************/
/***** CALLBACK METHODS *****/
/****************************/
@Override
public void onPreExecute() {
Log.i(TAG, "onPreExecute()");
mButton.setText(getString(R.string.cancel));
Toast.makeText(this, R.string.task_started_msg, Toast.LENGTH_SHORT).show();
}
@Override
public void onProgressUpdate(int percent) {
Log.i(TAG, "onProgressUpdate(" + percent + "%)");
mProgressBar.setProgress(percent * mProgressBar.getMax() / 100);
mPercent.setText(percent + "%");
}
@Override
public void onCancelled() {
Log.i(TAG, "onCancelled()");
mButton.setText(getString(R.string.start));
mProgressBar.setProgress(0);
mPercent.setText(getString(R.string.zero_percent));
Toast.makeText(this, R.string.task_cancelled_msg, Toast.LENGTH_SHORT).show();
}
@Override
public void onPostExecute() {
Log.i(TAG, "onPostExecute()");
mButton.setText(getString(R.string.start));
mProgressBar.setProgress(mProgressBar.getMax());
mPercent.setText(getString(R.string.one_hundred_percent));
Toast.makeText(this, R.string.task_complete_msg, Toast.LENGTH_SHORT).show();
}
Headless Fragment
public class TaskFragment extends Fragment {
public static final String TAG = TaskFragment.class.getSimpleName();
/**
* Callback interface through which the fragment can report the task's
* progress and results back to the Activity.
*/
static interface TaskCallbacks {
public void onPreExecute();
public void onProgressUpdate(int percent);
public void onCancelled();
public void onPostExecute();
}
public TaskCallbacks mCallbacks;
public DummyTask mTask;
public boolean mRunning;
/**
* Android passes us a reference to the newly created Activity by calling this
* method after each configuration change.
*/
@Override
public void onAttach(Activity activity) {
Log.i(TAG, "onAttach(Activity)");
super.onAttach(activity);
if (!(activity instanceof TaskCallbacks)) {
throw new IllegalStateException("Activity must implement the TaskCallbacks interface.");
}
// Hold a reference to the parent Activity so we can report back the task's
// current progress and results.
mCallbacks = (TaskCallbacks) activity;
}
/**
* This method is called only once when the Fragment is first created.
*/
@Override
public void onCreate(Bundle savedInstanceState) {
Log.i(TAG, "onCreate(Bundle)");
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
/**
* This method is <em>not</em> called when the Fragment is being retained
* across Activity instances.
*/
@Override
public void onDestroy() {
Log.i(TAG, "onDestroy()");
super.onDestroy();
cancel();
}
/*****************************/
/***** TASK FRAGMENT API *****/
/*****************************/
/**
* Start the background task.
*/
public void start() {
if (!mRunning) {
mTask = new DummyTask();
mTask.execute();
mRunning = true;
}
}
/**
* Cancel the background task.
*/
public void cancel() {
if (mRunning) {
mTask.cancel(false);
mTask = null;
mRunning = false;
}
}
/**
* Returns the current state of the background task.
*/
public boolean isRunning() {
return mRunning;
}
}
My Background Task
public class DummyTask extends AsyncTask<Void, Integer, Void> {
TaskCallbacks callbacks;
@Override
protected void onPreExecute() {
// Proxy the call to the Activity
callbacks.onPreExecute();
//fragment.mRunning = true;
}
@Override
protected Void doInBackground(Void... ignore) {
for (int i = 0; !isCancelled() && i < 100; i++) {
//Log.i(TAG, "publishProgress(" + i + "%)");
SystemClock.sleep(100);
publishProgress(i);
}
return null;
}
@Override
protected void onProgressUpdate(Integer... percent) {
// Proxy the call to the Activity
callbacks.onProgressUpdate(percent[0]);
}
@Override
protected void onCancelled() {
// Proxy the call to the Activity
callbacks.onCancelled();
//fragment.mRunning = false;
}
@Override
protected void onPostExecute(Void ignore) {
// Proxy the call to the Activity
callbacks.onPostExecute();
//fragment.mRunning = false;
}
}
My logCat
03-08 06:41:51.517: E/AndroidRuntime(20497): FATAL EXCEPTION: main
03-08 06:41:51.517: E/AndroidRuntime(20497): java.lang.NullPointerException
03-08 06:41:51.517: E/AndroidRuntime(20497): at com.adp.retaintask.DummyTask.onPreExecute(DummyTask.java:22)
03-08 06:41:51.517: E/AndroidRuntime(20497): at android.os.AsyncTask.executeOnExecutor(AsyncTask.java:586)
03-08 06:41:51.517: E/AndroidRuntime(20497): at android.os.AsyncTask.execute(AsyncTask.java:534)
03-08 06:41:51.517: E/AndroidRuntime(20497): at com.adp.retaintask.TaskFragment.start(TaskFragment.java:73)
03-08 06:41:51.517: E/AndroidRuntime(20497): at com.adp.retaintask.MainActivity$1.onClick(MainActivity.java:51)
03-08 06:41:51.517: E/AndroidRuntime(20497): at android.view.View.performClick(View.java:4102)
03-08 06:41:51.517: E/AndroidRuntime(20497): at android.view.View$PerformClick.run(View.java:17085)
03-08 06:41:51.517: E/AndroidRuntime(20497): at android.os.Handler.handleCallback(Handler.java:615)
03-08 06:41:51.517: E/AndroidRuntime(20497): at android.os.Handler.dispatchMessage(Handler.java:92)
03-08 06:41:51.517: E/AndroidRuntime(20497): at android.os.Looper.loop(Looper.java:155)
03-08 06:41:51.517: E/AndroidRuntime(20497): at android.app.ActivityThread.main(ActivityThread.java:5454)
03-08 06:41:51.517: E/AndroidRuntime(20497): at java.lang.reflect.Method.invokeNative(Native Method)
03-08 06:41:51.517: E/AndroidRuntime(20497): at java.lang.reflect.Method.invoke(Method.java:511)
03-08 06:41:51.517: E/AndroidRuntime(20497): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1029)
03-08 06:41:51.517: E/AndroidRuntime(20497): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:796)
03-08 06:41:51.517: E/AndroidRuntime(20497): at dalvik.system.NativeStart.main(Native Method)
thank you.