0

I'm using AsyncTask in one of the fragments of an activity.

The AsyncTask is working perfectly but on screen rotation, it loses the reference to activity and the variable returns NullPointerException thus the app crashes.

I looked at similar questions like this, this, this, and this, but I don't think using config change hack is the solution.

The code that maybe crashing the application (according to LogCat, the NullPointerException is at the following line):

Context context = MyActivity.this.getApplicationContext();

I've to pass the context in another function residing outside of the activity and fragment class.

Thank you in advance.

Update: My LogCat

09-29 09:40:53.415: E/AndroidRuntime(21997): FATAL EXCEPTION: AsyncTask #2
09-29 09:40:53.415: E/AndroidRuntime(21997): java.lang.RuntimeException: An error occured while executing doInBackground()
09-29 09:40:53.415: E/AndroidRuntime(21997):    at android.os.AsyncTask$3.done(AsyncTask.java:278)
09-29 09:40:53.415: E/AndroidRuntime(21997):    at java.util.concurrent.FutureTask$Sync.innerSetException(FutureTask.java:273)
09-29 09:40:53.415: E/AndroidRuntime(21997):    at java.util.concurrent.FutureTask.setException(FutureTask.java:124)
09-29 09:40:53.415: E/AndroidRuntime(21997):    at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:307)
09-29 09:40:53.415: E/AndroidRuntime(21997):    at java.util.concurrent.FutureTask.run(FutureTask.java:137)
09-29 09:40:53.415: E/AndroidRuntime(21997):    at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:208)
09-29 09:40:53.415: E/AndroidRuntime(21997):    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1076)
09-29 09:40:53.415: E/AndroidRuntime(21997):    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:569)
09-29 09:40:53.415: E/AndroidRuntime(21997):    at java.lang.Thread.run(Thread.java:856)
09-29 09:40:53.415: E/AndroidRuntime(21997): Caused by: java.lang.NullPointerException
09-29 09:40:53.415: E/AndroidRuntime(21997):    at com.example.CommonClasses.CommonFunctions.readFile(CommonFunctions.java:262)
09-29 09:40:53.415: E/AndroidRuntime(21997):    at com.example.CommonClasses.CommonFunctions.readFileContents(CommonFunctions.java:308)
09-29 09:40:53.415: E/AndroidRuntime(21997):    at com.example.android.AvailabilityFragment$AvailabilityData.doInBackground(AvailabilityFragment.java:160)
09-29 09:40:53.415: E/AndroidRuntime(21997):    at com.example.android.AvailabilityFragment$AvailabilityData.doInBackground(AvailabilityFragment.java:1)
09-29 09:40:53.415: E/AndroidRuntime(21997):    at android.os.AsyncTask$2.call(AsyncTask.java:264)
09-29 09:40:53.415: E/AndroidRuntime(21997):    at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:305)
Community
  • 1
  • 1
Basit
  • 1,830
  • 2
  • 31
  • 50
  • Hey you have to check if its null or not before performing any operation. – Panther Sep 29 '14 at 04:44
  • Post the logcat trace please – Aniruddha Sep 29 '14 at 04:44
  • @Panther It would always be null because activity is destroyed after screen rotation. Wouldn't that cause AsyncTask to not run after screen rotation? – Basit Sep 29 '14 at 04:47
  • Instead of `MyActivity.this.getApplicationContext();` use `Context.getApplicationContext()` or `Activity.getApplication()` – Panther Sep 29 '14 at 04:53
  • The null pointer occured here `CommonFunctions.readFile(CommonFunctions.java:262)` try to find what it is :-/ – Panther Sep 29 '14 at 04:54
  • @Panther That's the line of code I pasted in the question. – Basit Sep 29 '14 at 04:56
  • @Panther I'm in a fragment hence I have to use `getActivity().getApplicationContext()` which returns `null` on screen rotation. – Basit Sep 29 '14 at 05:00

2 Answers2

0

Try this way

<activity
    android:name=".ActivityName"
    android:configChanges="orientation|screenSize|keyboardHidden"/>
Biraj Zalavadia
  • 28,348
  • 10
  • 61
  • 77
  • 1
    I would rather not as it is NOT RECOMMENDED by the Android Developers guide: http://developer.android.com/guide/topics/manifest/activity-element.html#config – Basit Sep 29 '14 at 04:45
  • I know that is not recommended by the Android but it does not deny to use. If there is no multiple languages to support at runtime and keyboard stiffs you can use it. – Biraj Zalavadia Sep 29 '14 at 04:56
  • I have used it in many projects but don't have such big issue found due to this approach. – Biraj Zalavadia Sep 29 '14 at 04:57
  • I'd rather explore other not-so-hacky options first before even thinking about using it. Thank you for responding. – Basit Sep 29 '14 at 04:58
0

There are plenty of solutions to this. One simple one for you is to use what's known as a "Retain" fragment. This is an approach that is used in some of the Google samples. A retain fragment has no UI, and persists across orientation change. This is usually defined as a static inner class of your activity like so:

public static class RetainFragment extends Fragment {
    private static final String TAG = "RetainFragment";

    public RetainFragment() {}

    public static RetainFragment findOrCreateRetainFragment(FragmentManager fm) {
        RetainFragment fragment = (RetainFragment) fm.findFragmentByTag(TAG);
        if (fragment == null) {
            fragment = new RetainFragment();
        }
        return fragment;
    }

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

Then, put your AsyncTask inside of your RetainFragment. When onPostExecute is called, you can simply use the fragment's getActivity() method to get the Activity that it is attached to.

Hope that helps, let me know if there is still some confusion!

Edit: Here is a Google sample of a retain fragment

Edit 2: (as per comments)

public class A {

    // This class holds a reference to it's outer A instance. It can be
    // accessed using A.this.
    public class innerClassA {
        //...
    }

    // This class does not hold a reference to it's outer A instance.
    public static class innerClassB {
        //...
    }
}

Edit 3: In the comments, I ended up writing the full code anyway. For anyone interested:

public class MyActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my);

        Button b = (Button) findViewById(R.id.aaa);
        b.setOnClickListener(new View.OnClickListener() {
            @Override public void onClick(View v) {
                RetainFragment retainFragment =
                        RetainFragment.findOrCreateRetainFragment(getFragmentManager());
                retainFragment.loadAsync();
            }
        });
    }

    private void handleResponse(String response) {
        // do something with the response...
    }

    public static class RetainFragment extends Fragment {
        private static final String TAG = "RetainFragment";

        public RetainFragment() {
        }

        public static RetainFragment findOrCreateRetainFragment(FragmentManager fm) {
            RetainFragment fragment = (RetainFragment) fm.findFragmentByTag(TAG);
            if (fragment == null) {
                fragment = new RetainFragment();
                fm.beginTransaction().add(fragment, TAG).commit();
            }
            return fragment;
        }

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

        private void loadAsync() {
            new AsyncTask<Void, Void, String>() {
                @Override protected String doInBackground(Void... params) {
                    // Do some work...
                    return null;
                }

                @Override protected void onPostExecute(String response) {
                    MyActivity myActivity = (MyActivity)getActivity();
                    if (myActivity != null) {
                        myActivity.handleResponse(response);
                    }
                }
            }.execute();
        }
    }
}
Bradley Campbell
  • 9,298
  • 6
  • 37
  • 47
  • This looks elegant enough. Question: Do I have to use static class in order for this solution to work? – Basit Sep 29 '14 at 05:01
  • If it's not static you will have memory leaks – Bradley Campbell Sep 29 '14 at 05:09
  • Wouldn't making the fragment class static lead to some unforseen exceptions in the future? I don't know, I'm just throwing it out there. – Basit Sep 29 '14 at 05:14
  • Because this is not the only fragment I have in my application. There are many others and they are being called via creating an object of the fragment and passing it to `fragmentManager`. Some of these fragments are instantiated on the same activity. – Basit Sep 29 '14 at 05:16
  • Making the class static means that it won't keep a reference to it's containing class, i.e. the Activity in this case. This is not a problem. It would be a problem if it weren't static, as non-static inner classes keep an implicit strong reference to their containing class and hence would persist the Activity instance even after the System is done with it (causing a memory leak). – Bradley Campbell Sep 29 '14 at 05:53
  • Added a link to a RetainFragment sample by Google. Unfortunately their sample doesn't have a static inner class. I think that's a mistake on their part though ;) – Bradley Campbell Sep 29 '14 at 05:59
  • I'm sort of new to Android so I am having some troubles digesting it. When I applied your suggestion and renamed my fragment class to `static`, I got the following error: `Illegal modifier for the class MyFragment; only public, abstract & final are permitted` – Basit Sep 29 '14 at 05:59
  • An inner class is a class contained within another class. I'll edit with an example. – Bradley Campbell Sep 29 '14 at 06:01