Question

I have spent many hours looking for a solution to this and need help.

I have a nested AsyncTask in my Android app Activity and I would like to allow the user to rotate his phone during it's processing without starting a new AsyncTask. I tried to use onRetainNonConfigurationInstance() and getLastNonConfigurationInstance().

I am able to retain the task; however after rotation it does not save the result from onPostExecute() to the outer class variable. Of course, I tried getters and setters. When I dump the variable in onPostExecute, that it is OK. But when I try to access to the variable from onClick listener then it is null.

Maybe the code will make the problem clear for you.

public class MainActivity extends BaseActivity {

    private String possibleResults = null;
    private Object task = null;

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

            this.task = getLastNonConfigurationInstance();

            setContentView(R.layout.menu);

            if ((savedInstanceState != null)
                            && (savedInstanceState.containsKey("possibleResults"))) {
                    this.possibleResults = savedInstanceState
                                    .getString("possibleResults");
            }

            if (this.possibleResults == null) {
                    if (this.task != null) {
                            if (this.task instanceof PossibleResultWebService) {
                                    ((PossibleResultWebService) this.task).attach();
                            }

                    } else {
                            this.task = new PossibleResultWebService();
                            ((PossibleResultWebService) this.task).execute(this.matchToken);
                    }
            }

            Button button;
            button = (Button) findViewById(R.id.menu_resultButton);
            button.setOnClickListener(resultListener);
    }

    @Override
    protected void onResume() {
            super.onResume();
    }

    OnClickListener resultListener = new OnClickListener() {
            @Override
            public void onClick(View v) {

                    Spinner s = (Spinner) findViewById(R.id.menu_heatSpinner);
                    int heatNo = s.getSelectedItemPosition() + 1;
                    Intent myIntent = new Intent(MainActivity.this,
                                    ResultActivity.class);
                    myIntent.putExtra("matchToken", MainActivity.this.matchToken);
                    myIntent.putExtra("heatNo", String.valueOf(heatNo));
                    myIntent.putExtra("possibleResults",
                                    MainActivity.this.possibleResults);

                    MainActivity.this.startActivityForResult(myIntent, ADD_RESULT);
            }
    };

    private class PossibleResultWebService extends AsyncTask<String, Integer, Integer> {

            private ProgressDialog pd;
            private InputStream is;
            private boolean finished = false;
            private String possibleResults = null;

            public boolean isFinished() {
                    return finished;
            }

            public String getPossibleResults() {
                    return possibleResults;
            }

            @Override
            protected Integer doInBackground(String... params) {
                // quite long code
            }

            public void attach() {
                    if (this.finished == false) {
                            pd = ProgressDialog.show(MainActivity.this, "Please wait...",
                                            "Loading data...", true, false);
                    }
            }

            public void detach() {
                    pd.dismiss();
            }

            @Override
            protected void onPreExecute() {
                    pd = ProgressDialog.show(MainActivity.this, "Please wait...",
                                    "Loading data...", true, false);
            }

            @Override
            protected void onPostExecute(Integer result) {
                    possibleResults = convertStreamToString(is);
                    MainActivity.this.possibleResults = possibleResults;

                    pd.dismiss();
                    this.finished = true;
            }
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {

            super.onSaveInstanceState(outState);

            if (this.possibleResults != null) {
                    outState.putString("possibleResults", this.possibleResults);
            }

    }

    @Override
    public Object onRetainNonConfigurationInstance() {
            if (this.task instanceof PossibleResultWebService) {
                    ((PossibleResultWebService) this.task).detach();
            }
            return (this.task);
    }

}

Was it helpful?

Solution

It is because you are creating the OnClickListener each time you instantiate the Activity (so each time you are getting a fresh, new, OuterClass.this reference), however you are saving the AsyncTask between Activity instantiations and keeping a reference to the first instantiated Activity in it by referencing OuterClass.this.

For an example of how to do this right, please see https://github.com/commonsguy/cw-android/tree/master/Rotation/RotationAsync/

You will see he has an attach() and detach() method in his RotationAwareTask to solve this problem.

To confirm that the OuterClass.this reference inside the AsyncTask will always point to the first instantiated Activity if you keep it between screen orientation changes (using onRetainNonConfigurationInstance) then you can use a static counter that gets incremented each time by the default constructor and keep an instance level variable that gets set to the count on each creation, then print that.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top