0

I initiate most of my networking calls from Fragments and then use callbacks to tell the Fragment whether or not the networking task succeeded or failed and to update ui accordingly.

On rare occassions (.25% of sessions) my program is crashing with a null-pointer exception due to getActivity() returning null when the code in my callback runs. I know that I can use a null check on getActivity() to prevent this from happening, however what's the best practice for handling this issue?

The null check seems to be little more than a crash prevention tool as the program still needs the data from the networking task.

The code looks something like the following:

private void queryServer() {
    // networking task should query server for user id, if successful store it 
    // in user preferences to be accessed by fragment in callback
    new networkingTask(new VolleyCallback() {
        @Override
        public void onSuccess() {
            // code below needs null check on getActivity - but what else?
            mUserId = new UserPreferences(getActivity()).getUserId();
        }

        @Override
        public void onFail() {
            // booooo
        }
    });
}
Braden Holt
  • 1,544
  • 1
  • 18
  • 32
  • You should familiarize yourself with the lifecyle methods of fragments and activities. What is likely happening is the Activity/Fragment pair are being stopped or destroyed by the system. This will happen for a variety of reasons, such as a screen orientation change. Because your handler is a method on the fragment object, you are working with a "dead" fragment by the time the call returns. There are several patterns for dealing with this. In short you need to make your handler aware of the current fragment, and you can accomplish this by using lifecycle methods. – EJK Sep 28 '17 at 20:55

1 Answers1

2

As I stated in my comment above, what is likely happening is the Activity/Fragment pair are being stopped or destroyed by the system. This will happen for a variety of reasons, such as a screen orientation change. Because your handler is a method on the fragment object, you are working with a "dead" fragment by the time the call returns. There are several patterns for dealing with this. In short you need to make your handler aware of the current fragment, and you can accomplish this by using lifecycle methods.

Below is an example of a pattern you could use. I tried to make the example as minimal as possible.

import android.app.Activity;
import android.app.Fragment;

public class MyFragment extends Fragment {

    // This is static so that it will not go out of scope when the original
    // fragment is destroy.  This allows it to be access from all MyFragment
    // instances.
    static MyResponseProcessor processor = new MyResponseProcessor();

    // This will be the class that handles your network call.  
    public static class MyResponseProcessor {

        // This instance variable is alway a reference to the currently displayed fragment.
        private Fragment activeFragement;

        public void setActiveFragement(Fragment activeFragement) {
            this.activeFragement = activeFragement;
        }

        // This method, which is for demonstration purposes, shows how you would handle a network response.
        public void handleResponse(SomeResponseObject) {
            if (activeFragement != null) {
                // Now you can get the activity
                Activity activity = activeFragement.getActivity();
            } else {
                // Yes it is possible that there is no active fragment.  
                // If the user has stayed on the same screen, then the
                // fragment of interest will likely be re-created, and 
                // this window of time with no fragment will be brief.
                //
                // Note that this null-check is very different than the 
                // null-check you describe.  In your case the reference is
                // guaranteed to be null forever.  In this case, the reference
                // will eventually become non-null.
            }
        }
    }

    @Override
    public void onStart() {
        super.onStart();
        // At this point in the fragment lifecycle, the fragment is both running and is attached to an Activity.
        // Thus "getActivity" calls are safe from this point onward.
        processor.setActiveFragement(this);
    }

    @Override
    public void onStop() {
        super.onStop();
        // At this point in the fragment lifecycle, the fragment has been stopped and is about to lose its connection to the activity.
        // So after this point, calls to "getActivity" are probably not safe.
        // DISCLAIMER - I have not tested this.  You might want to do this in a
        // different method such as "onDestroyView()"
        processor.setActiveFragement(null);
    }
}
EJK
  • 12,332
  • 3
  • 38
  • 55
  • Can you explain why my reference would be guaranteed to be null forever? Both null checks rely on getActivity... – Braden Holt Sep 28 '17 at 22:39
  • The other problem is that if my callback code can't execute, there's certain instances in my application where it will cause the entire user action to fail as the callback is just a stop before another network call. – Braden Holt Sep 28 '17 at 23:19
  • As for why your reference would be guaranteed to be null forever. Because once the Android system has destroyed your Activity/Fragment, Android will likely create a new Activity/Fragment pair to take the place of the originals. As part of the destruction of the fragment, the connection to the activity is "unwired". The null return value of "getActivity()" is Android's way of telling you that the fragment is no longer within an activity. – EJK Sep 29 '17 at 02:22
  • I suggest you read up on how Android manages Activities and Fragments. See https://developer.android.com/guide/components/activities/activity-lifecycle.html, and https://developer.android.com/guide/components/fragments.html#Lifecycle – EJK Sep 29 '17 at 02:23
  • Now for your 2nd point - you need to make sure that the callback code executes or your app will have errors. I get that. In the approach I outlined, the the activity reference will likely only be null for a brief period, which makes the error very unlikely, but not impossible. So here's a suggestion... – EJK Sep 29 '17 at 02:28
  • 1
    Your handler appears to need the activity simply to get a value from it (userID). Your activity could save that value elsewhere (in a Singleton or in SharedPreferences). Then your handler would no longer need to access the activity. – EJK Sep 29 '17 at 02:30
  • The core issue here that I maybe didn't make as evident in my question isn't why this is happening, it's how to ensure my method completes successfully if the activity dies. SharedPreferences and Singleton both rely on the activity being alive (need a context). I do very much appreciate your help but it seems that we're crossing wires a bit. Your suggestion to make the handler independent from the activity might be the way to go.. – Braden Holt Sep 29 '17 at 17:02
  • 1
    SharedPreferences and Singletons do **not** rely on the activity being alive. (1) The singleton pattern has nothing to do with Android and thus will not require an Android context. (2) If you choose to go with SharedPreferences, yes you need a context, but no that context does not have to be that of the current activity. You could alway just use the Application context which never goes out of scope. Here is a post that describes how any class in your app can access app context at any time: https://stackoverflow.com/questions/2002288/static-way-to-get-context-on-android. – EJK Sep 29 '17 at 18:56