1

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:

  1. Have my AsyncTask in a Separate File (as an outer class).
  2. Start the AsyncTask from inside the Headless Fragment.
  3. Display a progress Dialog, instead of a progress bar.
  4. 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.

Community
  • 1
  • 1
KingBryan
  • 361
  • 4
  • 16

1 Answers1

0

You need to pass the callbacks and the fragment to the DummyTask.

/**
* Start the background task.
*/
public void start() {
if (!mRunning) {
  mTask = new DummyTask(this, mCallbacks);
  mTask.execute();
  mRunning = true;
}

And in DummyTask add a constuctor

public DummyTask (TaskFragment fragment, TaskCallbacks callbacks) {
  this.framgent = fragment;
  this.callbacks = callbacks;
} 

Then you can put the //fragment.mRunning = true; stuff back in an it should work as expected.

Alternatively you can add the DummyTask as a subclass to the TaskFragment and use the member variables (which will be accessible then)

pumpkee
  • 3,337
  • 2
  • 25
  • 34