Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"Fragment already added" occurs sometimes while using EventBus with ViewPager

I am using EventBus to navigate from one fragment to another after a button click. Sometimes and not reproducible the following error comes up:

Fatal Exception: java.lang.IllegalStateException: Fragment already added: FragmentQuestion3_2{82d0763} (e57d5f9c-c80b-4f99-a4f4-e844bdabdef5) id=0x7f0a0487}
       at androidx.fragment.app.FragmentStore.addFragment(FragmentStore.java:67)
       at androidx.fragment.app.FragmentManager.addFragment(FragmentManager.java:1563)
       at androidx.fragment.app.BackStackRecord.executeOps(BackStackRecord.java:405)
       at androidx.fragment.app.FragmentManager.executeOps(FragmentManager.java:2167)
       at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:1990)
       at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:1945)
       at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:1847)
       at androidx.fragment.app.FragmentManager$4.run(FragmentManager.java:413)
       at android.os.Handler.handleCallback(Handler.java:873)
       at android.os.Handler.dispatchMessage(Handler.java:99)
       at android.os.Looper.loop(Looper.java:216)
       at android.app.ActivityThread.main(ActivityThread.java:7211)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:494)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:975)

There are some answers to that problem but nothing worked for me since I am using EventBus.

Here is the Fragment that apparently already have been added:

public class FragmentQuestion3_2 extends BaseFragment {

    private ViewModelQuestionAnswer viewModel;
    private String category;
    private ImageView imageCategory;
    private TextView question3;

    PagerAdapter viewPagerAdapter;
    public NonSwipeAbleViewPager optionAnswerViewPager;

    private boolean isFragmentLoaded = false;

    public FragmentQuestion3_2() {
        // Required empty public constructor
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        EventBus.getDefault().register(this);
    }

    public static FragmentQuestion3_2 newInstance(String questionNumber) {
        return new FragmentQuestion3_2();
    }

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

        // init ViewModel
        viewModel = ViewModelProviders.of(requireActivity()).get(ViewModelQuestionAnswer.class);
    }

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

        View view = inflater.inflate(R.layout.fragment_question, container, false);

        imageCategory = view.findViewById(R.id.image_view_question);
        question3 = view.findViewById(R.id.question);
        optionAnswerViewPager = view.findViewById(R.id.option_answer_view_pager);

        // Gets the category so the ImageView can be updated accordingly
        viewModel.getCategory().observe(requireActivity(), new Observer<String>() {
            @Override
            public void onChanged(@Nullable String s) {

                category = s;

                setUpCategoryImage();

            }
        });

        // Gets the question
        viewModel.getQuestion3().observe(requireActivity(), new Observer<String>() {
            @Override
            public void onChanged(@Nullable String s) {
                question3.setText(s);

            }
        });

        return view;
    }

    @Override
    public void onResume() {
        super.onResume();
        if (!isFragmentLoaded) {
            isFragmentLoaded = true;
            init();
        }
    }

    private void initOptionAnswerViewPager() {
        viewPagerAdapter = new ViewPagerOptions3Round2(getChildFragmentManager(), 2);
        Utils.getInstance().changePagerScroller(optionAnswerViewPager);
        optionAnswerViewPager.setAdapter(viewPagerAdapter);
        optionAnswerViewPager.setPagingEnabled(false);
        optionAnswerViewPager.setOffscreenPageLimit(3);
    }

    @Override
    protected void setupListeners() {

    }

    @Subscribe
    public void setViewPagerPosition(SetViewPagerPosition setViewPagerPosition) {
        optionAnswerViewPager.setCurrentItem(setViewPagerPosition.getPosition(), true);
    }

    @Override
    public void init() {

        initOptionAnswerViewPager();

    }
    @Override
    public void onDetach() {
        EventBus.getDefault().unregister(this);
        super.onDetach();
    }
}

I am using this FragmentStatePagerAdapter:

public class QuestionsPagerAdapterRound2 extends FragmentStatePagerAdapter {
    private static final String QUESTION_NUMBER = "question_number";

    private int mNumOfTabs;
    private final List<Fragment> mFragmentList = new ArrayList<>();


    public QuestionsPagerAdapterRound2(FragmentManager fm, int NumOfTabs) {
        super(fm, FragmentStatePagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
        this.mNumOfTabs = NumOfTabs;
    }

    public void addFragment(Fragment fragment) {
        mFragmentList.add(fragment);
    }

    @Override
    public Fragment getItem(int position) {
        switch (position) {
            case 0:
                return FragmentCategory_2.newInstance("1");
            case 1:
                return FragmentQuestion1_2.newInstance("2");
            case 2:
                return FragmentQuestion2_2.newInstance("3");
            case 3:
                return FragmentQuestion3_2.newInstance("4");
            case 4:
                return FragmentQuestionResult_2.newInstance("5");

            default:
                return null;
        }
    }

    @Override
    public int getCount() {
        return mNumOfTabs;
    }

    @Override
    public int getItemPosition(Object object) {
        return PagerAdapter.POSITION_NONE;
    }
}

Within each question I have a second ViewPager, so there is a ViewPager within a ViewPager, here is the second ViewPager:

public class ViewPagerOptions3Round2 extends FragmentStatePagerAdapter {

    private int mNumOfTabs;
    private final List<Fragment> mFragmentList = new ArrayList<>();


    public ViewPagerOptions3Round2(FragmentManager fm, int NumOfTabs) {
        super(fm, FragmentStatePagerAdapter.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
        this.mNumOfTabs = NumOfTabs;
    }

    public void addFragment(Fragment fragment) {
        mFragmentList.add(fragment);
    }

    @Override
    public Fragment getItem(int position) {
        switch (position) {
            case 0:
                return FragmentOptions3.newInstance();
            case 1:
                return FragmentAnswer3_2.newInstance();
            default:
                return null;
        }
    }

    @Override
    public int getCount() {
        return mNumOfTabs;
    }

    @Override
    public int getItemPosition(Object object) {
        return PagerAdapter.POSITION_NONE;
    }
}

This is my class I call to open the next fragment:

public class SetQuestionAdapterPosition {

    private Integer position;

    public Integer getPosition() {
        return position;
    }

    public void setPosition(Integer position) {
        this.position = position;
    }

    public SetQuestionAdapterPosition(Integer position) {
        this.position = position;
    }

}

I am using this call EventBus.getDefault().post(new SetQuestionAdapterPosition(1));to open the next fragment. Can I use the following code to prevent the app to crash?

        try {
            EventBus.getDefault().post(new SetQuestionAdapterPosition(1));
        } catch (Exception e){
            Log.e("Answer2", e.toString());
        }

Is there something I can add in my code so before adding the fragment it checks if its already been added to prevent the error statement?

I appreciate every help and suggestion!

like image 623
Kaiser Avatar asked Jul 27 '20 14:07

Kaiser


1 Answers

EventBus can often introduce spaghetti code and this situation is likely caused by the event being triggered at an unintended time. You didn't post the entire logic around when EventBus.getDefault().post() happens, but I strongly recommend double-checking why it triggers the same fragment twice.

However, an easy (but hacky) fix is to check if the fragment was already added before adding it with Fragment.isAdded():

public void addFragment(Fragment fragment) {
    if (!fragment.isAdded()) {
        mFragmentList.add(fragment);
    }
}
like image 146
Sharp Avatar answered Nov 10 '22 01:11

Sharp