kanudo
kanudo

Reputation: 2219

Fragment based ViewPager as RecyclerView item produces Null Object Reference

I have assigned Fragment based ViewPager as RecyclerView item. The onBindViewHolder() inside RecyclerView.Adapter

produces

java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.TextView.setText(java.lang.CharSequence)' on a null object reference

while setting text using setText(). Means the view is not created at the time of binding the value.

I am not able to figure it out that -> why the view is not created, even though ViewPager and Fragment inflation is handled in onCreateViewHolder()? I tried inflation also in onBindViewHolder() but still the same.

How can i force Page Fragment inflation before onBindViewHolder().

RecyclerView.Adapter

public class MainListRVA extends RecyclerView.Adapter<ViewHolder> implements ConstantValues {

    FragmentManager oFm;

    private int viewPagerId = 1;

    private static ArrayList<MainListItem> oListItems = new ArrayList<MainListItem>();

    MainListRVA(ArrayList<MainListItem> theArray, FragmentManager fm){
        oListItems = theArray;
        oFm = fm;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_pager_main_list_item_folder, parent, false);
        return new FolderVH(v);
    }

    @Override
    public void onBindViewHolder(final ViewHolder holder, int position) {

        FolderVH folderVH = (FolderVH) holder;
        MainListItem listItem = oListItems.get(position);

        folderVH.fragmentOne.title.setText(listItem.oTitle);
        folderVH.fragmentTwo.title.setText(listItem.oDetail);
        /////////////////////////////////////////////////////
        ////////////////////THIS GIVE NULL POINTER EXCEPTION
    }

    public class FolderVH extends ViewHolder {

        FolderMainPageFragment fragmentOne, fragmentTwo;
        ViewPager viewPager;

        FolderVH(View v) {

            super(v);

            ArrayList<Fragment> fragments = new ArrayList<Fragment>();

            fragmentOne = FolderMainPageFragment.newInstance("one");
            fragmentTwo = FolderMainPageFragment.newInstance("two");

            fragments.add(fragmentOne);
            fragments.add(fragmentTwo);

            FolderVPAdapter folderVPAdapter = new FolderVPAdapter(oFm, fragments);

            viewPager = (ViewPager) v.findViewById(R.id.viewPager);
            viewPager.setId(viewPagerId++);
            viewPager.setAdapter(folderVPAdapter);
        }
    }

    @Override
    public int getItemCount() {
        return oListItems.size();
    }

    void move(int fromPos, int toPos){ }

    void remove(int position){ }
}

FragmentPagerAdapter

public class FolderVPAdapter extends FragmentPagerAdapter {

    private List<Fragment> fragments;

    public FolderVPAdapter(FragmentManager fm, List<Fragment> fragments) {
        super(fm);
        this.fragments = fragments;
    }

    @Override
    public Fragment getItem(int position) {
        return this.fragments.get(position);
    }

    @Override
    public int getCount() {
        return this.fragments.size();
    }
}

Pager Fragment

public class FolderMainPageFragment extends Fragment {

    TextView title;

    public static FolderMainPageFragment newInstance(String text) {
        return new FolderMainPageFragment();
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.main_list_item_view, container, false);
        this.title = (TextView) v.findViewById(R.id.list_title);
        return v;
    }
}

Upvotes: 2

Views: 763

Answers (3)

Abbas
Abbas

Reputation: 3331

Of course title is null.

FolderMainPageFragment hasn't even started its life cycle by the time you are accessing title. Which means onCreateView is not called yet and layout has not been inflated so has title.

As far as inflation in onCreateViewHolder() is concerned, you are merely inflating the layout view_pager_main_list_item_folder and not main_list_item_view (from FolderMainPageFragment which contains title) because the FolderVPAdapter hasn't even started yet.

Also why would you want to put a ViewPager inside a RecyclerView is beyond me? Perhaps you could do something else.

To pass data to Fragment write a method handleArgs() in FolderMainPageFragment.

public class FolderMainPageFragment extends Fragment {

    TextView title;
    Bundle mBundle;

    public void handleArgs(Bundle bundle) {
        mBundle = bundle;

        if (title != null)
            title.setText(bundle.getString("title"));
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.main_list_item_view, container, false);
        this.title = (TextView) v.findViewById(R.id.list_title);

        if (mBundle != null)        //    When handleArgs() is called before onCreateView().
            handleArgs(mBundle);
        return v;
    }
}

The advantage of writing your own Bundle method is that you can now use it in onBindViewHolder(). However you need to come up with a way where Bundle's new instance is not created in onBindViewHolder().

Upvotes: 0

kanudo
kanudo

Reputation: 2219

Here is the final solution that i have.

1) I added a condition to check if view is null while binding in onBindViewHolder

2) If null i just flagged boolean bindExceptionally as true inside the fragment class.

3) When fragment will create view at onCreateView() it will check if the flag is true to set the text. Else it will simply move ahead without setting text.

Note: fragments are inflated in onCreateViewHolder() and not in onBindViewHolder() hence fragments inflated only once and then they are recycled.

Updated - RecyclerView.Adapter

public class MainListRVA extends RecyclerView.Adapter<ViewHolder> implements ConstantValues {

    FragmentManager oFm;

    private int viewPagerId = 1;

    static ArrayList<MainListItem> oListItems = new ArrayList<MainListItem>();

    MainListRVA(ArrayList<MainListItem> theArray, FragmentManager fm){
        oListItems = theArray;
        oFm = fm;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_pager_main_list_item_folder, parent, false);
        return new FolderVH(v);
    }

    @Override
    public void onBindViewHolder(final ViewHolder holder, int position) {

        Log.e("onBindViewHolder", "onBindViewHolder");

        FolderVH folderVH = (FolderVH) holder;
        MainListItem listItem = oListItems.get(position);
        folderVH.viewPager.setCurrentItem(0);

        if(folderVH.fragmentOne.title != null) {
            folderVH.fragmentOne.title.setText(listItem.oTitle);
        } else {
            folderVH.fragmentOne.bindExceptionally = true;
            folderVH.fragmentOne.currListItem = listItem;
        }
    }

    public class FolderVH extends RecyclerView.ViewHolder {

        FolderMainPageFragment fragmentOne, fragmentTwo;
        ViewPager viewPager;

        FolderVH(View v) {

            super(v);

            Log.e("FolderVH", "FolderVH");

            ArrayList<Fragment> fragments = new ArrayList<Fragment>();

            fragmentOne = FolderMainPageFragment.newInstance("one");
            fragmentTwo = FolderMainPageFragment.newInstance("two");

            fragments.add(fragmentOne);
            fragments.add(fragmentTwo);

            FolderVPAdapter folderVPAdapter = new FolderVPAdapter(oFm, fragments);

            viewPager = (ViewPager) v.findViewById(R.id.viewPager);
            viewPager.setId(viewPagerId++);
            viewPager.setAdapter(folderVPAdapter);
        }
    }

    @Override
    public int getItemCount() {
        return oListItems.size();
    }

    void move(int fromPos, int toPos){ }

    void remove(int position){ }
}

Updated - Pager Fragment

public static class FolderMainPageFragment extends Fragment {

    TextView title;
    boolean bindExceptionally = false;
    MainListItem currListItem;

    public static FolderMainPageFragment newInstance(String text) {
        return new FolderMainPageFragment();
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.main_list_item_view_folder, container, false);
        this.title = (TextView) v.findViewById(R.id.list_title);

        if(bindExceptionally) {
            title.setText(currListItem.oTitle);
            bindExceptionally = false;
            currListItem = null;
        }

        return v;
    }
}

Upvotes: 1

Furedal
Furedal

Reputation: 533

As Abbas mentioned, the views inside the fragments is not initiated at the time when onBindViewHolder are called.

You should read a bit about fragments, if fragment is the way you want to go, then you should separate the view logic for the particular fragment inside the fragment. The only information you are supposed to pass to the fragments is the ones you put in the bundle you pass with setArguments at initiation of the fragment. There is a lot of lifecycle issues which will give you a pain otherwise.

In this case, if you want to set title to the fragments, you should instead do something like this at initiation:

Bundle args = new Bundle();
bundle.putString("title", "My title");
fragmentOne = FolderMainPageFragment.newInstance("one");
fragmentOne.setArguments(args);

And then in your fragments onViewCreated()

Bundle args = getArguments();
title.setText(args.getString("title"));

Upvotes: 0

Related Questions