Fragment布局的视图引发空指针异常。

huangapple go评论75阅读模式
英文:

Views of Fragment Layout causing NullPointerException

问题

我正在尝试使用带有Fragment State Adapter的View Pager。但在创建实例后尝试调用片段方法时出现“NullPointerException”。

以下是活动类和适配器类:

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

我的视图分页适配器是:

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;
        }
}

我的FragmentOverView类是:

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);
    }
}

但在创建FragmentOverview实例后调用setTargetView()方法时,我收到了“NullPointerException”。从日志中我注意到mTargetFrameLayout为空。

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

我认为这是因为活动的生命周期方法onCreate()onStart()onResume()在片段的生命周期方法onCreate()onCreateView()之前被调用。但是如何解决这个问题呢?或者我是不是在错误的方式中尝试做这件事?

英文:

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?

答案1

得分: 1

我认为你的假设是正确的。片段布局是在FragmentOverview类的onViewCreated()方法中生成的。但是这个方法在执行Activity生命周期方法onCreate()onStart()onResume()之后开始执行。你可以通过覆盖生命周期方法并按照@Max_Hockeborn的建议添加日志来查看ActivityFragment之间的生命周期关系。

因此,如果在FragmentOverviewonCreateView()执行之前调用setTargetView(),你将始终得到视图,即mTargetFrameLayout为空。

为了解决这个问题,你可以在父类中添加一个回调方法,在执行FragmentOverveiwonCreateView()后执行该回调。在回调中,你可以调用setTargetView()

下面是你的情况的一个可能解决方案:

FragmentOverview中添加以下代码:

public class FragmentOverview extends Fragment{
    //所有的字段;
    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) {
        //填充rootView并找到所有的子视图;
        //我认为你的代码是正确的;
    }

    //覆盖这个方法;
    //你也可以覆盖onViewCreated()生命周期方法;
    //然后从这里调用这个回调方法;
    @Override
    public void onResume() {
        super.onResume();
        callbacks.onReach();
    }

    public void setTargetView() {
        //做你想做的事情;
    }

    public interface OverviewCallbacks {
        void onReach();
    }
}

以上是FragmentOverView类的全部内容。

现在根据以下内容修改你的Activity类:

public class AddScheduleActivity extends AppCompatActivity {

    private FragmentOverview fragmentOverview;
    private FragmentTodo fragmentTodo;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        //你的代码

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

希望这样可以解决问题。如果有任何问题,请进行评论。

英文:

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.

答案2

得分: 0

这只是一个我还没有测试过的快速想法。你可以尝试将这个部分进行更改:

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();    //错误在这里

改成这样:

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();    //错误在这里

为了检查你的问题是否与调用顺序有关,你可以添加一些日志并使用 Logcat 检查它们被调用的顺序(假设你在使用 Android Studio)。类似这样:

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();    //错误在这里

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) {
            //添加一个子视图
            mTargetFrameLayout.setVisibility(View.VISIBLE);
        } else mTargetFrameLayout.setVisibility(View.GONE);
    }
}

最后一个代码片段可能对你有趣,以查看 onCreateView(以及导致问题的变量初始化)是否在调用 setTargetView() 函数之前完成。

英文:

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.

huangapple
  • 本文由 发表于 2020年9月13日 21:51:22
  • 转载请务必保留本文链接:https://go.coder-hub.com/63871568.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定