1

I am trying to create the page that have view pager with mvvm architecture.

So what i trying to do is showing the Recycler view inside the fragment of the first tab by using observable method.

But when i try to set the content is of the adapter inside my fragment i got

java.lang.NullPointerException: Attempt to invoke virtual method 'void com.xxxx.view.adapter.FoodListAdapter.setFoodList(java.util.List)' on a null object reference 

My FragmentStatePagerAdapter adapter

@Override
    public Fragment getItem(int position) {
        switch (position) {
            case 0:
                Log.d("Food Pager", "get Item");
                FoodListFragment foodListFragment = new FoodListFragment();
                return foodListFragment;
            case 1:
                RestaurantListFragment restaurantListFragment2 = new RestaurantListFragment();
                return restaurantListFragment2;
            case 2:
                RestaurantListFragment restaurantListFragment3 = new RestaurantListFragment();
                return restaurantListFragment3;
            default:
                return null;
        }
    } 

My activity

private void observeViewModel(final CategoryViewModel categoryViewModel) {
        // Observe Category Data
        categoryViewModel.getCategory().observe(this, new Observer<Category>() {
            @Override
            public void onChanged(@Nullable Category category) {
                // Update UI
                if (category != null) {
                    if (category.foods != null) {
                        startFoodListFragment(category.foods);
                    }
                } else {
                    Log.e(TAG, "Category Data is Null");
                }
            }
        });
    }

    private void startFoodListFragment(List<CategoryQuery.Food> foodList) {
        Log.d("startFoodListFragment", "yolo");
        FoodListFragment foodListFragment = new FoodListFragment();
        foodListFragment.setFoodList(foodList);
    }

So inside my Fragment

@Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {

        Log.d("Food LIst", "On create VIew");
        View rootView = inflater.inflate(R.layout.fragment_food_list, container, false);

        mRecyclerView = (RecyclerView) rootView.findViewById(R.id.recyclerview_food_list);
        // mLoadingIndicator = (ProgressBar) rootView.findViewById(R.id.food_list_loading_indicator);

        Integer gridColumns = 2;

        Float width = getScreenSize().get("width");

        if (width > 600) {
            gridColumns = 3;
        }

        // Create Pinterest Style RecyclerView
        StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(gridColumns, StaggeredGridLayoutManager.VERTICAL);
        mRecyclerView.setLayoutManager(layoutManager);

        // Cool. it's Very easy ,but margin on my LinearLayout didn’t seem to work. So here’s a quick fix.
        SpacesItemDecoration decoration = new SpacesItemDecoration(8);
        mRecyclerView.addItemDecoration(decoration);

        mFoodListAdapter = new FoodListAdapter(foodListAdapterOnClickHandler);
        mRecyclerView.setAdapter(mFoodListAdapter);

        return rootView;
    }

@Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        if (mFoodList != null) {
            mFoodListAdapter.setFoodList(mFoodList);
        }
    }

    public void setFoodList(List<CategoryQuery.Food> foodList) {
        Log.d(TAG, "setFoodlist");
        if (foodList != null) {
            mFoodList = foodList;
            mFoodListAdapter.setFoodList(mFoodList);
            // mFoodListAdapter.notifyDataSetChanged();
        }
    }

This is part of my adapter

public void setFoodList(final List<? extends CategoryQuery.Food> foodList) {
        if (this.foodList == null) {
            this.foodList = foodList;
            notifyItemRangeInserted(0, foodList.size());
        }
    }

This is the related Log that i got

D/Food Pager: get Item
D/Food LIst: On create VIew
D/startFoodListFragment: yolo
D/Food List Fragment: setFoodlist
D/AndroidRuntime: Shutting down VM
E/AndroidRuntime: FATAL EXCEPTION: main

Then the error goes on

java.lang.NullPointerException: Attempt to invoke virtual method 'void com.xxx.view.adapter.FoodListAdapter.setFoodList(java.util.List)' on a null object reference

So anyone knows why i got null, when calling FoodListAdapter even after i was initialise inside onCreateView?

Thanks for your help!

Varis Darasirikul
  • 3,907
  • 9
  • 40
  • 75

2 Answers2

1

What does the log tell us?

D/Food Pager: get Item

The FragmentStatePagerAdapter.getItem(int position) was called to show a FoodListFragment. This usually means the Fragment will be added to the screen, so its onCreateView() should be called next. Which is actually the case:

D/Food LIst: On create VIew

On the other hand, we have an Observer to listen for data changes. Every time it fires, startFoodListFragment() will be called:

D/startFoodListFragment: yolo

In this method, a new instance of FoodListFragment will be created. You try to pass the new data into it by calling setFoodList()

D/Food List Fragment: setFoodlist

But since this new instance is not about to be attached to the screen, its onCreateView() has not been called. So its adapter has not been instantiated. That's why the app crashes:

D/AndroidRuntime: Shutting down VM E/AndroidRuntime: FATAL EXCEPTION: main

What can you do?

You should pass the data into the correct instance of FoodListFragment. Or simply register the Observer inside FoodListFragment. A Fragment is a Lifecycle Owner just like an Activity, so I think this approach would work best for you.

Bö macht Blau
  • 12,820
  • 5
  • 40
  • 61
  • In this case, if i want to access the correct `FoodListFragment` instance, how can i do that? – Varis Darasirikul Feb 26 '19 at 16:03
  • @Varis Darasirikul - for example [this post](https://stackoverflow.com/q/54468167/5015207) does it by keeping a List of all Fragments and reusing them. But I'd really suggest setting up the Fragment with its own ViewModel and Observer. It's a much cleaner approach – Bö macht Blau Feb 26 '19 at 17:31
  • Thanks!, i already done it. By access correct instance from an activity, i have to go this way because on that activity i have 1 endpoint to send a part of data to many widget. That is why i can't observe the viewmodel inside 1 fragment. – Varis Darasirikul Feb 26 '19 at 17:37
  • @VarisDarasirikul - I see your point. Of course one could have both the Activity as well as the Fragment register an observer and listen to data changes. But I'm not really an architecture guru, so I'm not sure what's best. Anyway, good to hear you solved your issue :) – Bö macht Blau Feb 26 '19 at 17:40
0

In your activity you have this method startFoodListFragment() which creates a fragment and calls setFoodList() method on it which internally sets data to adapter.

As the adapter instance is being created on onCreateView() lifecycle method, the adapter would not be initialized at the time of object creation. so it will be null at that time that's why it is throwing NullPointerException. Make sure that when you access adapter, it must be after the onCreateView() method is executed so that you will never get NPE again!

Birju Vachhani
  • 6,072
  • 4
  • 21
  • 43