We have on our app a ViewPager with a FragmentPagerAdapter
which contains three fragments. Two of these fragments are composed with a Recyclerview for each one.
The first page (the fragment without a ViewPager) is displayed correctly. However, when the ViewPager tries to pre-load the next page (a RecyclerView), the app crashes because of a NullPointerException
with the following log :
java.lang.NullPointerException: Attempt to invoke virtual method 'boolean android.support.v7.widget.RecyclerView$ViewHolder.shouldIgnore()' on a null object reference
at android.support.v7.widget.RecyclerView.findMinMaxChildLayoutPositions(RecyclerView.java:2839)
at android.support.v7.widget.RecyclerView.dispatchLayout(RecyclerView.java:2626)
at android.support.v7.widget.RecyclerView.onLayout(RecyclerView.java:3011)
at android.view.View.layout(View.java:15684)
at android.view.ViewGroup.layout(ViewGroup.java:4981)
at android.support.v4.view.ViewPager.onLayout(ViewPager.java:1626)
at android.view.View.layout(View.java:15684)
at android.view.ViewGroup.layout(ViewGroup.java:4981)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:573)
at android.widget.FrameLayout.onLayout(FrameLayout.java:508)
at android.view.View.layout(View.java:15684)
at android.view.ViewGroup.layout(ViewGroup.java:4981)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1703)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1557)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1466)
at android.view.View.layout(View.java:15684)
at android.view.ViewGroup.layout(ViewGroup.java:4981)
at android.support.design.widget.CoordinatorLayout.layoutChild(CoordinatorLayout.java:1000)
at android.support.design.widget.CoordinatorLayout.onLayoutChild(CoordinatorLayout.java:710)
at android.support.design.widget.CoordinatorLayout.onLayout(CoordinatorLayout.java:724)
at android.view.View.layout(View.java:15684)
at android.view.ViewGroup.layout(ViewGroup.java:4981)
at android.support.v4.widget.DrawerLayout.onLayout(DrawerLayout.java:907)
at android.view.View.layout(View.java:15684)
at android.view.ViewGroup.layout(ViewGroup.java:4981)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:573)
at android.widget.FrameLayout.onLayout(FrameLayout.java:508)
at android.view.View.layout(View.java:15684)
at android.view.ViewGroup.layout(ViewGroup.java:4981)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1703)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1557)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1466)
at android.view.View.layout(View.java:15684)
at android.view.ViewGroup.layout(ViewGroup.java:4981)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:573)
at android.widget.FrameLayout.onLayout(FrameLayout.java:508)
at android.view.View.layout(View.java:15684)
at android.view.ViewGroup.layout(ViewGroup.java:4981)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1703)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1557)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1466)
at android.view.View.layout(View.java:15684)
at android.view.ViewGroup.layout(ViewGroup.java:4981)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:573)
at android.widget.FrameLayout.onLayout(FrameLayout.java:508)
at android.view.View.layout(View.java:15684)
at android.view.ViewGroup.layout(ViewGroup.java:4981)
at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:2186)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1920)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1106)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6018)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:792)
at android.view.Choreographer.doCallbacks(Choreographer.java:596)
at android.view.Choreographer.doFrame(Choreographer.java:557)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:778)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:155)
at android.app.ActivityThread.main(ActivityThread.java:5696)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
Here's how the ViewPager is declared :
ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager);
ViewPagerAdapter adapter = new ViewPagerAdapter(getSupportFragmentManager());
adapter.addFrag(fragment1, "fragment1");
adapter.addFrag(fragment2, "fragment2");
adapter.addFrag(fragment3, "fragment3");
viewPager.setAdapter(adapter);
And the adapter :
private class ViewPagerAdapter extends FragmentPagerAdapter {
private final List<Fragment> mFragmentList = new ArrayList<>();
private final List<String> mFragmentTitleList = new ArrayList<>();
public ViewPagerAdapter(FragmentManager manager) {
super(manager);
}
@Override
public Fragment getItem(int position) {
return mFragmentList.get(position);
}
@Override
public int getCount() {
return mFragmentList.size();
}
public void addFrag(Fragment fragment, String title) {
mFragmentList.add(fragment);
mFragmentTitleList.add(title);
}
@Override
public CharSequence getPageTitle(int position) {
return mFragmentTitleList.get(position);
}
}
As the code from both of the RecyclerView is long and different for each page I don't really know which part is relevant so I won't give any sample. Do not hesitate to ask for a specific part if you think it can be helpful to fix the issue.
One thing that I can tell you is that if I want it to work, I have to comment the call for each of the setAdapter
from both of the RecylerView.
EDIT : Here's the code for the second page.
public class MyFragment extends Fragment {
RecyclerView recyclerView;
GridAdapter gridAdapter;
public GridAdapter getGridAdapter() {
return gridAdapter;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final View v = inflater.inflate(R.layout.our_layout, container, false);
recyclerView = (RecyclerView) v.findViewById(R.id.recycler_view);
gridLayoutManager.setSmoothScrollbarEnabled(true);
recyclerView.setLayoutManager(gridLayoutManager);
recyclerView.setHasFixedSize(true);
return v;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
ArrayList<Model> model = getArguments().getParcelableArrayList("extra");
if (model != null && model.size() != 0) {
gridAdapter = new GridAdapter(model);
recyclerView.setAdapter(gridAdapter);
}
}
@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser && isResumed()){
onResume();
}
}
@Override
public void onResume() {
super.onResume();
if (!getUserVisibleHint())
return;
}
public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration {
private int spanCount;
private int spacingLeft;
private int spacingRight;
private int spacingTop;
private int spacingBottom;
private boolean includeEdge;
public GridSpacingItemDecoration(int spanCount, int spacingLeft, int spacingTop, int spacingRight, int spacingBottom, boolean includeEdge) {
this.spanCount = spanCount;
this.spacingLeft = spacingLeft;
this.spacingRight = spacingRight;
this.spacingTop = spacingTop;
this.spacingBottom = spacingBottom;
this.includeEdge = includeEdge;
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
int position = parent.getChildAdapterPosition(view); // item position
int column = position % spanCount; // item column
if (includeEdge) {
outRect.left = spacingLeft - column * spacingLeft / spanCount; // spacing - column * ((1f / spanCount) * spacing)
outRect.right = (column + 1) * spacingRight / spanCount; // (column + 1) * ((1f / spanCount) * spacing)
if (position < spanCount) { // top edge
outRect.top = spacingTop;
}
outRect.bottom = spacingBottom; // item bottom
} else {
outRect.left = column * spacingLeft / spanCount; // column * ((1f / spanCount) * spacing)
outRect.right = spacingRight - (column + 1) * spacingRight / spanCount; // spacing - (column + 1) * ((1f / spanCount) * spacing)
if (position >= spanCount) {
outRect.top = spacingTop; // item top
}
}
}
}
public class GridAdapter extends RecyclerView.Adapter<GridAdapter.ViewHolder> {
private ArrayList<Model> model;
public GridAdapter(ArrayList<Model> offer) {
super();
model = offer;
}
@Override
public ViewHolder onCreateViewHolder(final ViewGroup parent, int viewType) {
final View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_item, parent, false);
final ViewHolder holder = new ViewHolder(view);
return holder;
}
@Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
final Model currentOffer = model.get(position);
holder.category.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@SuppressLint("NewApi")
@SuppressWarnings("deprecation")
@Override
public void onGlobalLayout() {
int width = holder.category.getWidth();
ViewGroup.LayoutParams params = holder.appIcon.getLayoutParams();
params.width = width;
params.height = width;
holder.appIcon.setLayoutParams(params);
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN)
holder.itemView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
else
holder.itemView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
});
Picasso.with(getActivity().getApplicationContext()).
load(currentOffer.getApp_logo()).fit().centerCrop().into(holder.appIcon);
holder.appName.setText(currentOffer.getApp_name());
holder.category.setText(currentOffer.getApp_category());
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String marketURL = AndroidTools.getPlayStoreURL(currentOffer.getApp_store_id(), true);
UITools.launchUrl(getActivity(), marketURL);
}
});
}
@Override
public int getItemCount() {
return model.size();
}
class ViewHolder extends RecyclerView.ViewHolder {
private ImageView appIcon;
private TextView appName;
private TextView category;
public ViewHolder(View itemView) {
super(itemView);
appIcon = (ImageView)itemView.findViewById(R.id.item_icon);
appName = (TextView)itemView.findViewById(R.id.item_app_name);
category = (TextView)itemView.findViewById(R.id.item_category);
}
}
}
}
Any help is much appreciated.
I've got this error during one of my development. Have you checked that your RecyclerView in your XML files are correctly wrapped into another layout like a FrameLayout?
If not, it will crash only on a Viewpager and not on single fragment view.
This happens when you accidentally add views directly to the RecyclerView
. In my case, I used View.inflate
for a decorator layout with the RecyclerView
as the parent parameter, which automatically attaches it. RecyclerView
iterates over any children attached to it and expects all its view children to have ViewHolders
in the layout params, and will throw this NPE when a child's view holder is null.
This happens when you add elements directly under listView
or RecyclerView
in your xml layout file.
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</android.support.v7.widget.RecyclerView>
Here I have added a TextView inside RecyclerView which will throw me onLayout error
(caused by NullPointerException
).You shouldn't add elements directly under RecyclerView
or listView
.
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