user14269872
user14269872

Reputation:

Views of Fragment Layout causing NullPointerException

I'm trying to use View Pager with Fragment State Adapter. But getting a NullPointerException when trying to invoke a Fragment method after creating its instance.

Here is the activity class and Adapter class:

public class AddScheduleActivity extends AppCompatActivity {

    private FragmentOverview fragmentOverview;
    private FragmentTodo fragmentTodo;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_add_schedule);

        ViewPager2 viewPager = findViewById(R.id.viewPager_AddScheduleActivity);
        viewPager.setAdapter(new ViewPagerAdapter(getSupportFragmentManager(), getLifecycle()));

        fragmentOverview = new FragmentOverview();
        fragmentOverview.setTargetView();    //ERROR IS HERE

My view pager adapter is:

private class ViewPagerAdapter extends FragmentStateAdapter {

        public ViewPagerAdapter(@NonNull FragmentManager fragmentManager, @NonNull Lifecycle lifecycle) {
            super(fragmentManager, lifecycle);
        }

        @NonNull
        @Override
        public Fragment createFragment(int position) {
            switch (position) {
                case 0:
                    return fragmentOverview;
                case 1:
                    return fragmentTodo;
                default:
                    return null;
            }
        }

        @Override
        public int getItemCount() {
            return 2;
        }
}

My FragmentOverView class is:

public class FragmentOverview extends Fragment{
    private FrameLayout mTargetFrameLayout;
    private FrameLayout mDescriptionFrameLayout;

    public FragmentOverview() {
        
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.fragment_overview, container, false);
        mDescriptionFrameLayout = rootView.findViewById(R.id.descriptionView_frameLayout_OverviewFragment);
        mTargetFrameLayout = rootView.findViewById(R.id.targetView_frameLayout_OverviewFragment);
        return rootView;
    }

    public void setTargetView() {
        mTargetFrameLayout.removeAllViews();
        if (progress.maxProgress != null) {
            //ADD A CHILD VIEW
            mTargetFrameLayout.setVisibility(View.VISIBLE);
        } else mTargetFrameLayout.setVisibility(View.GONE);
    }
}

But when I call the setTargetView() method after creating instance of FragmentOverview, I'm getting a NullPointerException. From log I noticed that the mTargetFrameLayout is null.

Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.FrameLayout.removeAllViews()' on a null object reference

I think this is because the activity lifecycle methods onCreate(), onStart(), onResume() are called before the fragment lifecycle methods onCreate() & onCreateView(). But how to overcome this problem? Or am I trying to do in a wrong way?

Upvotes: 2

Views: 104

Answers (2)

Shahriar Zaman
Shahriar Zaman

Reputation: 938

I think your assumption is right. The fragment layout is generated in onViewCreated() of FragmentOverview class. But this method start executing after the Activity lifecycle methods onCreate(), onStart() and onResume() are executed. You can checkout the lifecycle relation of Activity and Fragment by overwriting lifecycle methods and adding logs as mentioned by @Max_Hockeborn.

So you will always get the views i.e mTargetFrameLayout null if you call setTargetView() before executing onCreateView() in FragmentOverview.

To solve this, you can add a callback method that will be executed in parent class after executing the onCreateView() of FragmentOverveiw. And in that callback you can invoke setTargetView().

Here I'm providing a possible solution for your case:

In FragmentOverview add this lines:

public class FragmentOverview extends Fragment{
    //all your fields;
    private OverviewCallbacks callbacks;

    public FragmentOverview(OverviewCallbacks callbacks) {
        this.callbacks = callbacks;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        //inflate the rootView and find all the childViews;
        //I think your codes are okey;
    }

    //Overwrite this method;
    //You can also overwrite onViewCreated() lifecycle method;
    //And add call this callback method from here;
    @Override
    public void onResume() {
        super.onResume();
        callbacks.onReach();
    }

    public void setTargetView() {
        //Do what you want to do;
    }

    public interface OverviewCallbacks {
        void onReach();
    }
}

Thats all for FragmentOverView class.

Now modify your Activity class as follows:

public class AddScheduleActivity extends AppCompatActivity {

    private FragmentOverview fragmentOverview;
    private FragmentTodo fragmentTodo;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        //Your codes

        fragmentOverview = new FragmentOverview(new FragmentOverview.OverviewCallbacks() {
            @Override
            public void onReach() {
                setTargetView();
            }
        });
    }
}

Hope this will work. Comment if anything gone wrong.

Upvotes: 1

Max H
Max H

Reputation: 31

This is just a fast idea that I couldn't test yet. You could try to change this:

public class AddScheduleActivity extends AppCompatActivity {

    private FragmentOverview fragmentOverview;
    private FragmentTodo fragmentTodo;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_add_schedule);

        ViewPager2 viewPager = findViewById(R.id.viewPager_AddScheduleActivity);
        viewPager.setAdapter(new ViewPagerAdapter(getSupportFragmentManager(), getLifecycle()));

        fragmentOverview = new FragmentOverview();
        fragmentOverview.setTargetView();    //ERROR IS HERE

to this:

public class AddScheduleActivity extends AppCompatActivity {

    private FragmentOverview fragmentOverview = new FragmentOverview();
    private FragmentTodo fragmentTodo;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_add_schedule);

        ViewPager2 viewPager = findViewById(R.id.viewPager_AddScheduleActivity);
        viewPager.setAdapter(new ViewPagerAdapter(getSupportFragmentManager(), getLifecycle()));

        
        fragmentOverview.setTargetView();    //ERROR IS HERE

To check your assumption that your problem is based on the call order you can add some logs and check the order they are called in with Logcat (given you use android studio) Something like this:

public class AddScheduleActivity extends AppCompatActivity {

    private FragmentOverview fragmentOverview;
    private FragmentTodo fragmentTodo;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_add_schedule);

        ViewPager2 viewPager = findViewById(R.id.viewPager_AddScheduleActivity);
        viewPager.setAdapter(new ViewPagerAdapter(getSupportFragmentManager(), getLifecycle()));

        Log.d("YOUR_TAG","Activity onCreate 1");

        fragmentOverview = new FragmentOverview();
        fragmentOverview.setTargetView();    //ERROR IS HERE

and

public class FragmentOverview extends Fragment{
    private FrameLayout mTargetFrameLayout;
    private FrameLayout mDescriptionFrameLayout;

    public FragmentOverview() {
        
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.fragment_overview, container, false);
        mDescriptionFrameLayout = rootView.findViewById(R.id.descriptionView_frameLayout_OverviewFragment);
        mTargetFrameLayout = rootView.findViewById(R.id.targetView_frameLayout_OverviewFragment);

Log.d("YOUR_TAG","FragmentOverview onCreateView 1");

        return rootView;
    }

    public void setTargetView() {

        Log.d("YOUR_TAG","FragmentOverview setTargetView 1");

        mTargetFrameLayout.removeAllViews();
        if (progress.maxProgress != null) {
            //ADD A CHILD VIEW
            mTargetFrameLayout.setVisibility(View.VISIBLE);
        } else mTargetFrameLayout.setVisibility(View.GONE);
    }
}

The last one could be interesting for you to see if onCreateView (and the initiation of the variables that cause your problems) is finished before the setTargetView() function is called.

Upvotes: 0

Related Questions