Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NullPointerException in FrameLayout.onMeasure()

Edit: As I've debugged this and tried various changes, I realized the original code I posted wasn't really relevant, so I removed it:

I have an Activity that can switch between 3 different Fragments when the user pushes one of 3 IconButtons I have inserted into the ActionBar in a custom layout, like switching between various modes:

enter image description here

When I first launch the app (having uninstalled it), I'm defaulted to the Globe mode. The Globe mode has several fragments in a ViewPager, most of these fragments are subclasses of the android.support.v4.app.ListFragment. I am creating all 8 of my tabs in the main Activity's onCreate method. This works as expected, and everything loads properly.

When I go to the "Profile" mode (by clicking on the Face iconbutton), I can log into the app, and see my profile. This also works as expected, everything is loading properly.

When I push the APK again from Android Studio, since I am now logged in (saved Preferences), it defaults me to the "friend feed" mode (the two people holding hands). I can switch modes to the Profile, and everything works as expected, but when I switch to the Globe mode, it is crashing in Android code:

08-14 14:43:22.744  28804-29323/com.trover E/AndroidRuntime﹕ FATAL EXCEPTION: main
    Process: com.trover, PID: 28804
    java.lang.NullPointerException
            at android.widget.FrameLayout.onMeasure(FrameLayout.java:309)
            at android.view.View.measure(View.java:16497)
            at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5125)
            at com.android.internal.widget.ActionBarOverlayLayout.onMeasure(ActionBarOverlayLayout.java:327)
            at android.view.View.measure(View.java:16497)
            at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5125)
            at android.widget.FrameLayout.onMeasure(FrameLayout.java:310)
            at com.android.internal.policy.impl.PhoneWindow$DecorView.onMeasure(PhoneWindow.java:2291)
            at android.view.View.measure(View.java:16497)
            at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:1912)
            at android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:1109)
            at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1291)
            at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:996)
            at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:5600)
            at android.view.Choreographer$CallbackRecord.run(Choreographer.java:761)
            at android.view.Choreographer.doCallbacks(Choreographer.java:574)
            at android.view.Choreographer.doFrame(Choreographer.java:544)
            at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:747)
            at android.os.Handler.handleCallback(Handler.java:733)
            at android.os.Handler.dispatchMessage(Handler.java:95)
            at android.os.Looper.loop(Looper.java:136)
            at android.app.ActivityThread.main(ActivityThread.java:5001)
            at java.lang.reflect.Method.invokeNative(Native Method)
            at java.lang.reflect.Method.invoke(Method.java:515)
            at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:785)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:601)
            at dalvik.system.NativeStart.main(Native Method)

In the past two weeks I have made two major changes to the project:

  1. I have removed the ActionbarSherlock library. I originally was hoping to remove the support.v4 library as well until I realized I needed it for the ViewPager.
  2. I switched from Eclipse to Android Studio, which included moving all of the files to the new Android Studio structure.

This morning I re-installed Eclipse and went to the point right after removing ActionbarSherlock, and could not recreate this crash.

Things I have tried:

  • Manually building the project with Gradle, and manually installing it and running it. When I do the same steps, it crashes in the same way.
  • Running this through the debugger, and stopping at the crash. Line 309 is the following:

        if (mMeasureAllChildren || child.getVisibility() != GONE) {
    

At this point in the code, child is null, however, the call to getChildCount() on line 296 is returning a value of 2. I still have no idea which View is actually crashing at this point, there is no defining value in any of the variable as I browse through them in the debugger.

  • When you log in/out, items that are in the overflow menu are changed. When you click on the IconButtons on the top, I call setBackgroundDrawable on the IconButtons to set the yellow highlight (or set null if it's not the active button anymore). When I comment out both the code in onCreateOptionsMenu and the code in setHighlightedIconButton(), the crash no longer occurs
  • When I remove all of the fragments from the ViewPager, the crash no longer occurs
  • As @kcoppock mentioned below, it could likely be that I'm improperly inflating something somewhere. Following the article he linked, I modified every call in my app where I was calling inflater.inflate(R.layout.somelayout, null) instead to inflater.inflate(R.layout.somelayout, viewGroupContainer, false), except in the case where I am creating custom views on the tabs (where I don't know the viewGroup to parent them under). In that case I'm manually setting LayoutParameters.

I'm completely at a loss at this point as to what to even try next.

update: Changing the app to always start in the Globe Mode prevents the crash, so it seems to be something with the switch to the Globe Mode if we didn't start with it active.

Here is my code to build the Tabs for the main Activity's viewPager:

    // We HAVE to read this value out before creating the layout, or onTabSelected will set it back to zero
    final SharedPreferences prefs = getApplicationContext().getSharedPreferences(
            Const.Preferences.PREFS_FILE, Context.MODE_PRIVATE);
    boolean deniedLocationServices = prefs.getBoolean(Const.Preferences.LOCATION_SERVICES_DENIED, false);
    int previousTab = prefs.getInt(Const.Preferences.PREVIOUS_TAB, 0);

    setContentView(R.layout.main_browse);

    mFragmentManager = getSupportFragmentManager();

    mActionBar = getActionBar();
    mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);

    // these three lines hide the 'Trover' logo
    mActionBar.setDisplayHomeAsUpEnabled(false);
    mActionBar.setDisplayUseLogoEnabled(false);
    mActionBar.setIcon(new ColorDrawable(android.R.color.black));
    mActionBar.setCustomView(R.layout.action_bar_icons);
    mActionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM | ActionBar.DISPLAY_SHOW_HOME);
    final int numTabs = 8;

    TabbedBrowsePagerTab[] tabs = new TabbedBrowsePagerTab[numTabs];

    tabs[mAllTabPosition] = buildAllTab();
    tabs[mWhatsHotTabPosition] = buildWhatsHotTab();
    tabs[mWhatsNewTabPosition] = buildWhatsNewTab();
    tabs[mJumpToTabPosition] = buildJumpToTab();
    tabs[mFoodTabPosition] = buildFoodTab();
    tabs[mOutdoorTabPosition] = buildOutdoorTab();
    tabs[mArtTabPosition] = buildArtTab();
    tabs[mLatestTabPosition] = buildLatestTab();

    mPagerAdapter = new TabbedBrowsePagerAdapter(this, tabs);
    mViewPager = (ViewPager) findViewById(R.id.pager);
    mViewPager.setAdapter(mPagerAdapter);

    mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
        @Override
        public void onPageSelected(final int position) {
            try {
                mActionBar.setSelectedNavigationItem(position);
            } catch (final IllegalStateException e) {
                TroverApplication.logError(TAG, "IllegalArgumentsException setting tab position in PageChangeListener");
            }
        }
    });

    // Build the actual tabs on the actionbar
    for (int i = 0; i < tabs.length; i++) {
        mActionBar.addTab(mActionBar.newTab()
                .setTabListener(this));

        LayoutInflater inflater = LayoutInflater.from(this);
        View customView = inflater.inflate(R.layout.custom_action_bar_tabs, null);
        customView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
        TextView tabCustom = (TextView) customView.findViewById(R.id.custom_action_bar_tab);
        tabCustom.setText(tabs[i].getTabTitle());
        tabCustom.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.CENTER_VERTICAL);
        tabCustom.setTypeface(TroverApplication.getDefaultFontBold());
        mActionBar.getTabAt(i).setCustomView(tabCustom);
    }

    // Now set up the button listeners for those icons
    mActionBar.getCustomView().findViewById(R.id.action_bar_friend_feed_button).setOnClickListener(this);
    mActionBar.getCustomView().findViewById(R.id.action_bar_me_button).setOnClickListener(this);
    mActionBar.getCustomView().findViewById(R.id.action_bar_nearby_button).setOnClickListener(this);

    mViewPager.setCurrentItem(previousTab);

    TroverLocationManager manger = TroverLocationManager.get();
    if (!manger.isNetworkLocationEnabled() && !deniedLocationServices) {
        showLocationServicesDialog();
    }

    if (AuthManager.get().isAuthenticated()) {
        mCurrentMode = Mode.NEWS_MODE; // <----- if I change this to GLOBE_MODE it doesn't crash
    } else {
        mCurrentMode = Mode.GLOBE_MODE;
    }
    validateView();

Here is the main_browse.xml layout:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_height="fill_parent"
    android:layout_width="fill_parent" >

    <android.support.v4.view.ViewPager
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <Button
        android:id="@+id/main_camera_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_marginBottom="15dp"
        android:layout_marginRight="15dp"
        android:background="@drawable/camera_floating" />

</RelativeLayout>

Here is the setHighlightedIconButton function:

private void setHighlightedIconButton() {
    ImageButton button;
    View iconContainer = mActionBar.getCustomView();

    PaintDrawable selectedBackground = new PaintDrawable(getResources().getColor(R.color.trover_selected_icon_background));

    button = (ImageButton) iconContainer.findViewById(R.id.action_bar_friend_feed_button);
    if (mCurrentMode == Mode.NEWS_MODE) {
        button.setBackgroundDrawable(selectedBackground);
        button.setClickable(false);
    } else {
        button.setBackgroundDrawable(null);
        button.setClickable(true);
    }

    button = (ImageButton) iconContainer.findViewById(R.id.action_bar_nearby_button);
    if (mCurrentMode == Mode.GLOBE_MODE) {
        button.setBackgroundDrawable(selectedBackground);
        button.setClickable(false);
    } else {
        button.setBackgroundDrawable(null);
        button.setClickable(true);
    }

    button = (ImageButton) iconContainer.findViewById(R.id.action_bar_me_button);
    if (mCurrentMode == Mode.PROFILE_MODE) {
        button.setBackgroundDrawable(selectedBackground);
        button.setClickable(false);
    } else {
        button.setBackgroundDrawable(null);
        button.setClickable(true);
    }
}

And this is the validateView() function which calls it:

/**
 * Handles transitions between different fragments by checking the current mode and authentication state.
 * This function can handle being called multiple times, and will always try to do the least work possible
 * each time.
 */
private void validateView() {
    invalidateOptionsMenu();
    setHighlightedIconButton();

    boolean authenticated = AuthManager.get().isAuthenticated();

    switch(mCurrentMode) {
        case GLOBE_MODE:
            // Note - we don't record a screen here because the tabs will do that
            removeMeFragment();
            removeNewsFragment();
            removeOnboardingFragment();
            mViewPager.setVisibility(View.VISIBLE);
            mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
            mPagerAdapter.madeVisible();
            break;
        case NEWS_MODE:
            if (mPreviousTabPosition == mJumpToTabPosition) {
                InputMethodManager imm = (InputMethodManager) getSystemService(
                        Context.INPUT_METHOD_SERVICE);
                imm.hideSoftInputFromWindow(mViewPager.getWindowToken(), 0);
            }
            recordScreen(getCurrentModeTrackingString(), this);
            removeMeFragment();
            removeOnboardingFragment();
            if (mNewsFeedFragment == null) {
                mNewsFeedFragment = DiscoveryListFragment.newNewsFeedInstance();
                mFragmentManager.beginTransaction().add(android.R.id.content, mNewsFeedFragment).commit();
            }
            mActionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
            mViewPager.setVisibility(View.GONE);
            break;
        case PROFILE_MODE:
            if (mPreviousTabPosition == mJumpToTabPosition) {
                InputMethodManager imm = (InputMethodManager) getSystemService(
                        Context.INPUT_METHOD_SERVICE);
                imm.hideSoftInputFromWindow(mViewPager.getWindowToken(), 0);
            }
            if (authenticated) {
                recordScreen(getCurrentModeTrackingString(), this);
                removeNewsFragment();
                removeOnboardingFragment();
                if (mProfileFragment == null) {
                    mProfileFragment = UserDetailFragment.newMeInstance();
                    mFragmentManager.beginTransaction().add(android.R.id.content, mProfileFragment).commit();
                }
            } else {
                recordScreen(getCurrentModeTrackingString() + "/onboarding", this);
                removeMeFragment();
                removeNewsFragment();
                if (mOnboardingFragment == null) {
                    removeOnboardingFragment();
                    mOnboardingFragment = new OnboardingFragment();
                    mFragmentManager.beginTransaction().add(android.R.id.content, mOnboardingFragment).commit();
                }
            }
            mViewPager.setVisibility(View.GONE);
            break;
        default:
            TroverApplication.logError(TAG, "Invalid mode!");
    }
}

Here is the onCreateOptionsMenu for the main Activity:

public boolean onCreateOptionsMenu(final Menu menu) {
    MenuInflater inflater = getMenuInflater();
    inflater.inflate(R.menu.menu, menu);
    if (!AuthManager.get().isAuthenticated()) {
        MenuItem item = menu.findItem(R.id.menu_notifications);
        if (item != null) {
            item.setVisible(false);
        }
        item = menu.findItem(R.id.menu_recommended_users);
        if (item != null) {
            item.setVisible(false);
        }
    }
    return true;
}

This is the part of the FrameLayout.onMeasure() function that is part of the Android API 19 source code where it is crashing:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int count = getChildCount();  <-- this is returning 2

    final boolean measureMatchParentChildren =
            MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
            MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
    mMatchParentChildren.clear();

    int maxHeight = 0;
    int maxWidth = 0;
    int childState = 0;

    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        if (mMeasureAllChildren || child.getVisibility() != GONE) { <-- crash happens here
            measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            maxWidth = Math.max(maxWidth,
                    child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
            maxHeight = Math.max(maxHeight,
                    child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
            childState = combineMeasuredStates(childState, child.getMeasuredState());
            if (measureMatchParentChildren) {
                if (lp.width == LayoutParams.MATCH_PARENT ||
                        lp.height == LayoutParams.MATCH_PARENT) {
                    mMatchParentChildren.add(child);
                }
            }
        }
    }
like image 648
Carl Anderson Avatar asked Aug 14 '14 21:08

Carl Anderson


Video Answer


2 Answers

View customView = inflater.inflate(R.layout.custom_action_bar_tabs, null);

This is likely the source of your problem. If you pass in null, the inflater doesn't know what type of LayoutParams to generate (it generates them from the parent ViewGroup). I believed that this generated default ViewGroup.LayoutParams, but maybe it doesn't provide any LayoutParams at all.

You should replace this with:

View customView = inflater.inflate(R.layout.custom_action_bar_tabs, parent, false);

where parent is the ViewGroup into which customView will be added. If you don't have an available parent, then you can set some custom LayoutParams manually:

View customView = inflater.inflate(R.layout.custom_action_bar_tabs, null);
customView.setLayoutParams(new ViewGroup.LayoutParams(
    ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
like image 133
Kevin Coppock Avatar answered Oct 23 '22 15:10

Kevin Coppock


Issue 2:

Since the exception is raised during layout, two calls in the validateView method look iffy. Replace the two calls like this: mViewPager.setVisibility(View.GONE); with mViewPager.setVisibility(View.INVISIBLE);

The transition between gone and visible when globe mode is entered is triggering layout. Going from invisible to visible will not trigger layout and that is a good guess for where a NPE happens.

Update. Based on your comment, let's suppose the gone-->visible transition is just triggering a NPE in a different layout, ie in your custom action bar.

By dumping the view hierarchy of an example app (Eclipse > DDMS > Dump View Hierachy for View Automator), I found a few FrameLayouts in my action bar, which is interesting. My example app is not using your custom view of course, but even in the icon / home part of the action bar there is an ImageView inside a frame layout.

So, try replacing this tricky code:

// these three lines hide the 'Trover' logo
mActionBar.setDisplayHomeAsUpEnabled(false);
mActionBar.setDisplayUseLogoEnabled(false);
mActionBar.setIcon(new ColorDrawable(android.R.color.black));
mActionBar.setCustomView(R.layout.action_bar_icons);
mActionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM|ActionBar.DISPLAY_SHOW_HOME);

with this simpler code that should do the same thing in a safe way:

mActionBar.setCustomView(R.layout.action_bar_icons);
mActionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);

If that's not it, dumping your actual view and investigating the FrameLayouts in your custom action bar should be helpful.

like image 35
x-code Avatar answered Oct 23 '22 15:10

x-code