0

I'm an iOS dev, with absolutely no experience with android, so forgive me if I have difficulty expressing my question:

I'm getting a Fatal Exception in an android app I'm tasked with maintaining.

Fatal Exception: java.lang.RuntimeException Unable to start activity ComponentInfo{…}: java.lang.NullPointerException

I can see from the crash report (attached below) that the crash is happening in one of my fragments in a method called "bindLocation"

Here is the line it's crashing on:

            mLocationHeaderTextView.setVisibility(View.VISIBLE);

So clearly the problem is that mLocationHeaderTextview is null. mLocationHeaderTextView is injected using roboguice as follows:

 @InjectView(R.id.filter_location_header)
TextView mLocationHeaderTextView;

Now, I think the error is caused by my textView not being 'injected'. After looking at the stack trace, I've determine that this is likely a result of onViewCreated() not getting called before bindLocation() is called.

This is where I'm getting lost, however. I'm very unfamiliar with android, so I'm not sure how it's possible that my onViewCreated method is getting skipped over. I've tried a million things on the device to try and reproduce this situation, but I am unable. No matter what user actions I can think to try, my onViewCreated method gets called and my variable is non-Null. Perhaps I'm misreading the stack trace. I'm hoping that my stack trace below provides enough information for y'all to help me, but if not, let me know what else I can tell you about the app. I cannot post my full source code (as it does not belong to me), but I will provide whatever information I can.

Thanks so much for your help!

enter image description here

From my FilterDrawerFragment:

 @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragment_filter_drawer, container, false);

    ButterKnife.inject(this, view);

    return view;
}

@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);

    mRulesHeaderReset.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            resetRules();
        }
    });

    if (getActivity() != null && mDrawerLayout == null) {
        mDrawerLayout = (DrawerLayout) getActivity().findViewById(R.id.filter_drawer_layout);

        if (mDrawerLayout != null) {
            mDrawerLayout.setDrawerListener(new DrawerLayout.DrawerListener() {
                @Override
                public void onDrawerSlide(View view, float v) {

                }

                @Override
                public void onDrawerOpened(View view) {
                    NavigationUtils.invalidateOptionsMenu(getActivity());
                }

                @Override
                public void onDrawerClosed(View view) {
                    notifyFiltersChanged();

                    NavigationUtils.invalidateOptionsMenu(getActivity());
                }

                @Override
                public void onDrawerStateChanged(int i) {

                }
            });

            // set a custom shadow that overlays the main content when the drawer opens
            mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.END);
        }
    }

    mlocationChangeButton.setOnClickListener(this);

    bindUI();
}

 private void bindUI() {
    if (isAdded() && mConfiguration != null) {

        // bind the location
        bindLocation();
        …
    }
}

private void bindLocation() {
    if (isAdded() && mConfiguration != null && mConfiguration.isLocationEnabled()) {

        // show the location related items
        mLocationHeaderTextView.setVisibility(View.VISIBLE);//Crash reported here.
        …
            }
        });

    }
}


/**
 * Users of this fragment must call this to update the filter configuration
 */
public void updateConfiguration(FilterConfiguration configuration) {
    if (configuration != null && !configuration.equals(mConfiguration)) {
        mConfiguration = configuration;

        bindUI();
    }
}

And updateConfiguration() is called from LocationFinderFragment:

 @Override
public void onViewCreated(View view, Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);

    if (getArguments() != null) {
          ………
    if (savedInstanceState == null) {
        mFilterDrawerFragment = FilterDrawerFragment.newInstance();
        getChildFragmentManager()
                .beginTransaction()
                .replace(R.id.filter_drawer, mFilterDrawerFragment)
                .commit();

    } else {
        mFilterDrawerFragment = (FilterDrawerFragment) getChildFragmentManager().findFragmentById(R.id.filter_drawer);
    }

    ………         
);
    …………………
    populateFilters();
}


private void populateFilters() {
    // these might be passed into the fragment
    if (mFacets != null) {
        FilterConfiguration config = new FilterConfiguration() {{
        ……………………
        }};

        mFilterDrawerFragment.updateConfiguration(config);
Kelly Bennett
  • 725
  • 5
  • 27
  • try if(mLocationHeaderTextView!=null)mLocationHeaderTextView.setVisibility(View.VISIBLE); Let me know if that solves the problem. – Eugene H Apr 18 '15 at 03:38
  • I'm aware of how to check for null variables, but I'm not really trying to fix the mere fact of this variable being null, I'm trying to understand what is causing it to be null. – Kelly Bennett Apr 18 '15 at 03:40
  • I am assuming it is crashing while you are trying to retain the state? – Eugene H Apr 18 '15 at 03:42
  • Not sure. I'm unable to reproduce the crash myself. The last line of the main-thread's stack trace is: android.app.ActivityThread.performLaunchActivity (ActivityThread.java:2394) – Kelly Bennett Apr 18 '15 at 03:46
  • Sorry misinterpreted your last statement.I didnt realize you can't reproduce the crash. – Eugene H Apr 18 '15 at 03:49
  • I am just trying to find out during which part of the lifecycle this error occurred. Can you post some code where the this error is occurring? – Eugene H Apr 18 '15 at 03:55
  • I haven't used roboguice, but according to their documentation in fragments injection happens in `onCreateView()` (not `onCreate()`) and the fragment must extend `RoboFragment` – AndroidEx Apr 18 '15 at 04:05
  • @Android777, sorry, You're correct. I meant to say onCreateView(). I'll update my question. Also, my fragment does extend RoboFragment. – Kelly Bennett Apr 18 '15 at 04:12
  • @EugeneH, I've added some code. Note sure if it's what you needed to see. Let me know what else I can provide. – Kelly Bennett Apr 18 '15 at 04:20
  • Okay, what locations in the fragment are you calling bindUI(); ? I think that may point to the location of the problem – Eugene H Apr 18 '15 at 04:27
  • @EugeneH I added the place where bindUI() is eventually getting called from above. – Kelly Bennett Apr 18 '15 at 05:09

2 Answers2

0

Now I think I see the problem.

  1. In roboguice views are injected in onViewCreated().
  2. onViewCreated() is called after onCreateView().
  3. bindLocation() is called in onCreateView().

Hence, by this time views are not yet injected by roboguice.

Please try calling bindLocation() in onViewCreated() after call to super.

AndroidEx
  • 15,524
  • 9
  • 54
  • 50
  • I've added my onCreateView() and onViewCreated() methods in my question above. bindLocations is getting called in onViewCreated() – Kelly Bennett Apr 18 '15 at 04:52
  • So you also use butterknife in the same project? In this case which library injects your views in the fragment? I would say one injecting library is too much to start with, but two with same annotation names... Whose @InjectView is used on the textview? – AndroidEx Apr 18 '15 at 04:58
  • I wouldn't use any injecting libraries if it was up to me, but I am taking over this (very large) project, so I have no choice. Anyway, the @injectView is ButterKnife. I've added a bit more code above. Hopefully it helps! – Kelly Bennett Apr 18 '15 at 05:04
  • @Ragnar, ok, now it's clearer, so another fragment calls a method on our fragment that requires a created layout. Our fragment is created, but has no layout at this point of execution, that's why there's NPE. – AndroidEx Apr 18 '15 at 05:15
  • ok great! Forgive my ignorance, but I'm not familiar enough with Android to know what you mean when you say "no layout". Is there a problem with the way the mFilterDrawerFragment is being created in my LocationFinderFragment? – Kelly Bennett Apr 18 '15 at 05:17
  • @Ragnar please correct me if I'm wrong but you have a fragment inside a fragment, right? By "no layout" I mean it somehow hasn't passed onCreateView stage. I'm afraid, I will have to take a break now, since it's quite late in my time zone, but I'll look into it in the morning if the problem is not resolved by that time. – AndroidEx Apr 18 '15 at 05:23
  • Thanks so much for your help thus far! I'll look at this in the morning as well, when I've got a fresher brain and try to understand what's going on a little better. I think you're on to something though! – Kelly Bennett Apr 18 '15 at 05:27
  • 1
    @RagnarDanneskjöld hey, I'm thinking about this line `if (savedInstanceState == null)`, and it's my new suspect. Could you please try moving the replace transaction just after the `if` block so it happens no matter what? I'm talking about 4 lines starting with `getChildFragmentManager()...`. – AndroidEx Apr 18 '15 at 17:15
  • The same pattern (checking savedInstanceState, replacing fragments, etc) is used all throughout this app, so if there is a problem with this pattern, I'd very much like to understand what it is. I think part of the reason I can't get savedInstanceState to be non-null might be that I'm debugging from an emulator. I'll try some things from an actual device tomorrow. I could try moving the replace transaction, as you say, but I'll have no way to verify that it's a solution until I understand specifically what the problem is, and how to reproduce it. – Kelly Bennett Apr 19 '15 at 19:05
  • @RagnarDanneskjöld fair enough. But Android is not *that* predictable, some things you just get to know by an experiment, which I'm unable to do without the app apparently. If I had it I would do the following measurements: put 2 breakpoints in `if (savedInstanceState == null)` statement, one in the main flow, one in the `else` part. The 3rd breakpoint is put in `onViewCreated()`. Now start rotating your device/emulator to test a hypothesis: `onViewCreated()` is **called** when execution goes through the main flow, and is **not called** when going through `else`. – AndroidEx Apr 19 '15 at 19:33
  • @RagnarDanneskjöld If this hypothesis is true, my previous comment should help. – AndroidEx Apr 19 '15 at 19:33
  • Good news and bad news. I've now figured out how to reproduce this bug with 100% reliability—bad news is, moving those 4 lines outside the if block does not fix the issue. – Kelly Bennett Apr 21 '15 at 22:13
  • @RagnarDanneskjöld ok, can you tell us how it is reproduced? :) – AndroidEx Apr 21 '15 at 22:23
  • In the device's settings->developer options menu I enabled the "Don't keep activities" option. Now all I have to do is navigate to the Activity with the LocationFinderFragement, hit the home button, and then re-activate my app. The else block gets called and mFilterDrawerFragment is non-null, but all of its injected views are null. You're earlier suggestion was essentially correct though, if I get rid of the if statement, and always go through what was the `savedInstanceState == null` path, FilterDrawerFragment.newInstance() get's called and the crash is no more. – Kelly Bennett Apr 21 '15 at 22:47
  • This seems like a reasonable thing to do since that if statement is just there to stop the allocation of an object which should already be allocated. So, while a bit wasteful perhaps, it does save me from crashing. I think the root problem must be either the way mFilterDrawerFragment is retrieved in the else block, or the way it's stored before the fragment get's destroyed. – Kelly Bennett Apr 21 '15 at 22:48
  • @RagnarDanneskjöld I still feel the main problem lies in the usage of nested fragments. Normally **activity** calls `onCreateView()` of the attached fragment during its own creation stage. But I cannot find any confirmation that a **fragment** has to call `onCreateView()` of its attached/nested fragment (perhaps, it'll have to in case it has `` tag in its xml, but when added programmatically - big question). That's why we need to call `onCreateView()` manually, but of course not directly, and we use a replace transaction to induce `onCreateView()` in the nested fragment. – AndroidEx Apr 21 '15 at 23:38
  • What you say sounds very plausible. Just to be sure that I understand the situation—getting rid of the if statement and going through what is now the `savedInstanceState == null` path is at worst just causing a few more CPU cycles yes? Putting anymore work/thought into this just to save those CPU cycles is definitely not worth the effort. If I understand this correctly, I'm going to close this issue and accept your answer as correct since you did lead me to a solution. – Kelly Bennett Apr 21 '15 at 23:44
  • @RagnarDanneskjöld yep, the most time-consuming thing in this case is the fragment layout creation but it happens in either case no matter what, and the fragment creation itself should be practically unnoticeable in terms of latency. – AndroidEx Apr 21 '15 at 23:57
0

This is how I fixed this. As you are using ButterKnife. Without these Configuration ButterKnife throws NPE.

Before you use this library you have to do some configuration to ButterKnife Eclipse Configuration or ButterKnife Android Studio Configuration

If you are using ADT and missing Annototion Processing Check out this to install Android Eclipse - Cannot see annotation processing option

Bharatesh
  • 8,943
  • 3
  • 38
  • 67