2

I've an AppCompatActivity that uses the NavigationDrawer pattern, managing some fragments. In one of these, that has no setRetainInstance(true), I show a DialogFragment with a ProgressDialog inside and an AsyncTask with this code:

SavingLoader savingLoader = SavingLoader.newInstance(savingLoaderMaxValue);
savingLoader.show(getChildFragmentManager(), SAVING_LOADER_TAG);
new MyAsyncTask().execute();

Where the SavingLoader class is this one:

public class SavingLoader extends DialogFragment {

    private static final String MAX_VALUE_TAG = "MAX_VALUE_TAG";
    private static final String PROGRESS_VALUE_TAG = "PROGRESS_VALUE_TAG";

    public static SavingLoader newInstance(int max_value){
        SavingLoader s = new SavingLoader();
        Bundle args = new Bundle();
        args.putInt(MAX_VALUE_TAG, max_value);
        s.setArguments(args);
        return s;
    }

    private ProgressDialog dialog;

    public SavingLoader(){}

    @Override
    public void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setCancelable(false);
    }

    @NonNull
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState){
        dialog = new ProgressDialog(getActivity(), getTheme());
        dialog.setTitle(getString(R.string.dialog_title_saving));
        dialog.setMessage(getString(R.string.dialog_message_saving));
        dialog.setIndeterminate(false);
        int max = (savedInstanceState == null ?
            getArguments().getInt(MAX_VALUE_TAG) : savedInstanceState.getInt(MAX_VALUE_TAG));
        if (max >= 1){
            dialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
            dialog.setProgress((savedInstanceState == null ?
                0 : savedInstanceState.getInt(PROGRESS_VALUE_TAG)));
            dialog.setMax(max);
        } else dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
        return dialog;
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt(MAX_VALUE_TAG, dialog.getMax());
        outState.putInt(PROGRESS_VALUE_TAG, dialog.getProgress());
    }

    public int getProgress(){
        return dialog.getProgress();
    }

    public int getMax(){
        return dialog.getMax();
    }

    public void incrementProgressBy(int value){
        if (dialog.getProgress() + value <= dialog.getMax())
            dialog.incrementProgressBy(value);
    }

}

In the onPostExecute() method I need to perform some UI update so here's my problem: if I start the dialog and the AsyncTask (like above) and I don't rotate my phone, all works as expected. Same thing if I rotate phone AFTER the onPostExecute() method. But if I rotate my phone WHILE the AsyncTask is still running, when it completes and reach the onPostExecute() method it gives me the IllegalStateException saying that the fragment hosting the AsyncTask and the Dialogfragment is no longer attached to the activity. So I tried to override both the onAttach() and the onDetach() methods (with a simple System.out.println) of my fragment, to see when the onPostExecute() gets called. The result is that when I rotate my phone, I always got this output:

onDetach
onAttach
... (if I rotate more my phone)
onPostExecute

So shouldn't the fragment be attached when the AsyncTask completes? Thank you all for your time and attention.

MatteoBelfiori
  • 280
  • 3
  • 13
  • I would suggest using just a `ProgressDialog`, because you are in just inflating a dialog in a `DialogFragment`. It seems redundant and there is so much extra code. Also, you need to check if your `fragment` is still attached to your `activity` in `onPostExecute` using `isFinishing`, http://developer.android.com/reference/android/app/Activity.html#isFinishing(). – Jared Burrows Jun 25 '15 at 18:20
  • Hi Jared, thank you for your answer. For the ProgressDialog suggestion I think I'll definitely follow it, so thanks. For the `isFinishing` method, I've tried to put a `System.out.println(getActivity().isFinishing())` in the `onPostExecute`: if I don't rotate my phone it writes "false" on the console and, in fact, all works as expected. But if I rotate my phone, my app crash because `getActivity()` returns null. – MatteoBelfiori Jun 25 '15 at 19:04
  • Yes, because the activity is recreated. So you need a `if (isFisinishing()) return;`. – Jared Burrows Jun 25 '15 at 21:27
  • But I need to refresh my UI, I can't simply return. I need to show user the AsyncTask results. – MatteoBelfiori Jun 26 '15 at 06:49
  • Actually in your `onCreate` have you tried `setRetainInstance(boolean)`? I use this in all my apps. So use a regular fragment + dialog.http://stackoverflow.com/questions/8417885/android-fragments-retaining-an-asynctask-during-screen-rotation-or-configuratio – Jared Burrows Jun 26 '15 at 13:04
  • I tried it, but using `setRetainInstance(true)` introduce some other complications (for example, my DialogFragment stops update it's ProgressDialog after rotations). If possible I'd like to avoid it. – MatteoBelfiori Jun 26 '15 at 16:34

1 Answers1

2

I've finally managed to solve this problem by stop using AsyncTask and using LoaderManager + AsyncTaskLoader following this article. In short, your fragment must implement the LoaderCallbacks interface and manage the AsyncTaskLoader. A skeleton fragment could be something like this:

public class MyFragment extends Fragment implements LoaderManager.LoaderCallbacks {

    @Override
    public View onCreateView(LayoutInflater inflater, final ViewGroup container,
                         Bundle savedInstanceState) {
        // Inflate here your view as you usually do and find your components
        // For example imagine to have a button tha will fire the task
        Button b = (Button) view.findViewById(R.id.my_button);
        b.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // Use this to start task for the first time
                getLoaderManager().initLoader(0, null, this);
                // .. or this for restart the task, details in
                // the provided article
                // getLoaderManager().restartLoader(0, null, this);
            }
        });

        // Get fragments load manager
        LoaderManager lm = getLoaderManager();
        if (lm.getLoader(0) != null) {
            // Reconnect to an existing loader
            lm.initLoader(0, null, this);
        }

        // Return your view here
        return view;
    }

    // LoaderCallbacks methods to override
    @Override
    public Loader onCreateLoader(int id, Bundle args) {
        // Create an instance of the loader passing the context
        MyTaskLoader loader = new MyTaskLoader(getActivity());
        loader.forceLoad();
        return loader;
    }

    @Override
    public void onLoadFinished(Loader loader, Object data) {
        // Use this callback as you would use the AsyncTask "onPostExecute"
    }

    @Override
    public void onLoaderReset(Loader loader) {}

    // Now define the loader class
    private static class MyTaskLoader extends AsyncTaskLoader {
        public MyTaskLoader(Context context){
            super(context);
        }

        @Override
        public Object loadInBackground() {
            // Do here your async work
        }
    }

}
MatteoBelfiori
  • 280
  • 3
  • 13