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 Fragment
s when the user pushes one of 3 IconButton
s I have inserted into the ActionBar in a custom layout, like switching between various modes:
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:
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:
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.
IconButton
s 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 occursViewPager
, the crash no longer occursinflater.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);
}
}
}
}
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));
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With