-1

I have an activity which consists of three fragments. In one of these fragments I have a viewpager that consists of three other fragments. I call getChildFragmentManager() inside fragment that has viewpager in it. I have searched SO and read almost every answer that are related to this topic for example: this and this and one that seemed most useful was this but could not solve my problem. I think if i can just return POSITION_NONE for getItemPosition(Object object) in FragmentStatePagerAdapter and call notifyDataSetChanged() will solve my problem but every time I do this my app crashes with this error:

java.lang.IllegalStateException: FragmentManager is already executing transactions!!!

and I mentioned that I am using getChildFragmentManager() inside my fragment. Anyone thinks of any solution to this problem?

this is the parent fragment:

public class WordDetailFragment extends Fragment {

    @BindView(R.id.word_parts_tab_layout)
    TabLayout mTabLayout;
    @BindView(R.id.word_part_view_pager)
    ViewPager mViewPager;

    private WordPagerAdapter mPagerAdapter;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_word_detail, container, false);
        ButterKnife.bind(this, view);
        mPagerAdapter = new WordPagerAdapter(getChildFragmentManager(), getActivity());
        mViewPager.setAdapter(mPagerAdapter);
        mTabLayout.setupWithViewPager(mViewPager);
        return view;
    }

    public void notifyTranslateChanged() {
        mViewPager.getAdapter().notifyDataSetChanged();
    }
}

this is the code for adapter

public class WordPagerAdapter extends FragmentStatePagerAdapter {
    final int PAGE_COUNT = 3;
    private String[] tabTitles = new String[]{"Definition", "Example", "Col & fam"};
    private Context context;

    public WordPagerAdapter(FragmentManager fm, Context context) {
        super(fm);
        this.context = context;
    }

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

    @Override
    public Fragment getItem(int position) {
        Fragment fragment;
        switch (position) {
            case 0:
                fragment = new WordDefinitionFragment();
                break;
            case 1:
                fragment = new WordExampleFragment();
                break;
            case 2:
                fragment = new WordColFamFragment();
                break;

                default: fragment = new WordDefinitionFragment();
        }
        return fragment;
    }

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

    @Override
    public CharSequence getPageTitle(int position) {
        // Generate title based on item position
        return tabTitles[position];
    }
}

One of the fragments in viewpager

public class WordDefinitionFragment extends Fragment {


    @BindView(R.id.show_translate_switch)
    Switch mShowTranslateSwitch;


    private List<WordInfo> mWordInfoList;
    private List<Definition> mDefinitionList;
    private boolean mShowTranslation;
    private View mView;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        mView = inflater.inflate(R.layout.fragment_word_definition, container, false);
        ButterKnife.bind(this, mView);

        mShowTranslation = SharedPrefHelper.getBooleanInfo(getActivity(), getString(R.string.show_translate_key));

        mShowTranslateSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
                if (getActivity() != null)
                    ((WordActivity) getActivity()).changeTranslateMode(b);
                showTranslation(b);
            }
        });

        return mView;
    }
}

and my activity. U have just included the parts that i felt are needed here.

public class WordActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceSate) {
        super.onCreate(savedInstanceSate);
        setContentView(R.layout.activity_word);


    }

    public void changeTranslateMode(boolean show) {
        SharedPrefHelper.setBooleanInfo(this, getString(R.string.show_translate_ke)y, show);
        ((WordDetailFragment)mWordDetailFragment).notifyTranslateChanged();
    }

}

and my logcat error is here

  • Please show the code where you call `notifyDataSetChanged`. – kalabalik Dec 01 '17 at 11:41
  • @KalaBalik it is in WordDetailFragment class inside notifyTranslateChanged method. – Mahdi Abolfazli Dec 01 '17 at 12:39
  • I have seen this, but from where do you call `notifyTranslateChanged`? – kalabalik Dec 01 '17 at 12:42
  • I call it from a method inside activity. I will edit my question in a hour with that code. – Mahdi Abolfazli Dec 01 '17 at 12:56
  • @KalaBalik I have editet my question and provided my activity and fragment. – Mahdi Abolfazli Dec 01 '17 at 13:52
  • So a view in one of your inner fragments (inside the `ViewPager`) gets checked and you propagate this event to the activity where you call `notifyDataSetChanged`. Why do you do this? Your data set has not been changed. Know what: You cannot change your data set because you have none. The way your `getItem` method in the adapter is set up (and which is totally okay) does not require a data set (like a list of fragments or ids). So no need to call `notifyDataSetChanged`. What is it you want to achieve with this call? – kalabalik Dec 01 '17 at 14:19
  • @KalaBalik I change the content of fragments in viewpager other than the fragment that is being shown. when a switch button is checked or unchecked I enable or disable the persian tranlation in all other fragments and i should check or uncheck the switch itself. I return POSITION_NONE and then notifyDataSetChanged to recreate all fragments to take the desired effect. – Mahdi Abolfazli Dec 01 '17 at 14:31
  • Can you show a stacktrace of the error please? – kalabalik Dec 01 '17 at 14:35
  • @KalaBalik I added the photo in my question – Mahdi Abolfazli Dec 01 '17 at 14:44
  • Hm, this looks like a bug in the support library you can find documented in [many places](https://www.google.de/search?q=fragmentmanagerimpl.ensureexecready). Sorry, but I am afraid I can't help you with this one. A workaround (and actually better solution) would be to manually change the current views. If I understand correctly, a switch to another fragment in the pager would have the correct language anyway since your fragment get recreated every time the pager is used (and your language "switch" is somehow "global"). – kalabalik Dec 01 '17 at 15:32
  • @KalaBalik I didn't get your last sentences. "A workaround (and actually better solution) would be to manually change the current views. If I understand correctly, a switch to another fragment in the pager would have the correct language anyway since your fragment get recreated every time the pager is used (and your language "switch" is somehow "global")." The problem is fragments are not recreated when switching between viewpager pages. I do not get how to MANUALLY change the current views. I'd appreciate if you give me some clue about this. – Mahdi Abolfazli Dec 01 '17 at 15:44
  • You are right, the direct neighbors of the fragments are not recreated (pos 0: 1 not recreated, 2 recreated, pos 1: 0 and 2 not recreated, pos 2: 0 recreated, 1 not recreated). I forgot about offscreenPageLimit (number of left or right neighbors) which is always at least 1. Regarding my workaround, refreshing views "manually" rather than resetting the adapter or calling `onDataSetChanged` is the way to go. If you have a `TextView` in your fragment, you simply call `setText` with new text, if you have a `Switch` you set it checked etc. More tedious, but also cheaper than recreating fragments. – kalabalik Dec 01 '17 at 16:39
  • @KalaBalik I tried this solution but the fact is when i try this it says fragment is not attached to the activity. or view is null. errors like this. do you have any code that managed to do this and i can take a look at it to know how to implement this scenario? – Mahdi Abolfazli Dec 01 '17 at 18:05
  • Use `viewPager.setOffscreenPageLimit(2)` just after you create your `ViewPager`. – kalabalik Dec 01 '17 at 19:03

1 Answers1

0

After many searches I could figure out a way which is not very smart but for the time being has solved my problem. First of all I used the class FixedFragmentStatePagerAdapter from this post. I also had to remove the part in which the fragment bundle is restored because it caches fragment variables and views.

public abstract class FixedFragmentStatePagerAdapter extends PagerAdapter {
    private static final String TAG = "PagerAdapter";
    private static final boolean DEBUG = false;

    private final FragmentManager mFragmentManager;
    private FragmentTransaction mCurTransaction = null;

    private ArrayList<Fragment.SavedState> mSavedState = new ArrayList<>();
    private ArrayList<String> mSavedFragmentTags = new ArrayList<>();
    private ArrayList<Fragment> mFragments = new ArrayList<>();
    private Fragment mCurrentPrimaryItem = null;

    public FixedFragmentStatePagerAdapter(FragmentManager fm) {
        mFragmentManager = fm;
    }

    /**
     * Return the Fragment associated with a specified position.
     */
    public abstract Fragment getItem(int position);

    public String getTag(int position) {
        return null;
    }

    @Override
    public void startUpdate(ViewGroup container) {
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        // If we already have this item instantiated, there is nothing
        // to do.  This can happen when we are restoring the entire pager
        // from its saved state, where the fragment manager has already
        // taken care of restoring the fragments we previously had instantiated.
        if (mFragments.size() > position) {
            Fragment f = mFragments.get(position);
            if (f != null) {
                return f;
            }
        }

        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }

        Fragment fragment = getItem(position);
        String fragmentTag = getTag(position);
        if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment + " t=" + fragmentTag);
       /* if (mSavedState.size() > position) {
            String savedTag = mSavedFragmentTags.get(position);
            if (TextUtils.equals(fragmentTag, savedTag)) {
                Fragment.SavedState fss = mSavedState.get(position);
                if (fss != null) {
                    fragment.setInitialSavedState(fss);
                }
            }
        }*/
        while (mFragments.size() <= position) {
            mFragments.add(null);
        }
        fragment.setMenuVisibility(false);
        fragment.setUserVisibleHint(false);
        mFragments.set(position, fragment);
        mCurTransaction.add(container.getId(), fragment, fragmentTag);

        return fragment;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        Fragment fragment = (Fragment)object;

        if (mCurTransaction == null) {
            mCurTransaction = mFragmentManager.beginTransaction();
        }
        if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object
                + " v=" + ((Fragment)object).getView() + " t=" + fragment.getTag());
        while (mSavedState.size() <= position) {
            mSavedState.add(null);
            mSavedFragmentTags.add(null);
        }
        mSavedState.set(position, mFragmentManager.saveFragmentInstanceState(fragment));
        mSavedFragmentTags.set(position, fragment.getTag());
        mFragments.set(position, null);

        mCurTransaction.remove(fragment);
    }

    @Override
    public void setPrimaryItem(ViewGroup container, int position, Object object) {
        Fragment fragment = (Fragment)object;
        if (fragment != mCurrentPrimaryItem) {
            if (mCurrentPrimaryItem != null) {
                mCurrentPrimaryItem.setMenuVisibility(false);
                mCurrentPrimaryItem.setUserVisibleHint(false);
            }
            if (fragment != null) {
                fragment.setMenuVisibility(true);
                fragment.setUserVisibleHint(true);
            }
            mCurrentPrimaryItem = fragment;
        }
    }

    @Override
    public void finishUpdate(ViewGroup container) {
        if (mCurTransaction != null) {
            mCurTransaction.commitAllowingStateLoss();
            mCurTransaction = null;
            mFragmentManager.executePendingTransactions();
        }
    }

    @Override
    public boolean isViewFromObject(View view, Object object) {
        return ((Fragment)object).getView() == view;
    }

    @Override
    public Parcelable saveState() {
        Bundle state = null;
        if (mSavedState.size() > 0) {
            state = new Bundle();
            Fragment.SavedState[] fss = new Fragment.SavedState[mSavedState.size()];
            mSavedState.toArray(fss);
            state.putParcelableArray("states", fss);
            state.putStringArrayList("tags", mSavedFragmentTags);
        }
        for (int i=0; i<mFragments.size(); i++) {
            Fragment f = mFragments.get(i);
            if (f != null) {
                if (state == null) {
                    state = new Bundle();
                }
                String key = "f" + i;
                mFragmentManager.putFragment(state, key, f);
            }
        }
        return state;
    }

    @Override
    public void restoreState(Parcelable state, ClassLoader loader) {
        if (state != null) {
            Bundle bundle = (Bundle)state;
            bundle.setClassLoader(loader);
            Parcelable[] fss = bundle.getParcelableArray("states");
            mSavedState.clear();
            mFragments.clear();

            ArrayList<String> tags = bundle.getStringArrayList("tags");
            if (tags != null) {
                mSavedFragmentTags = tags;
            } else {
                mSavedFragmentTags.clear();
            }
            if (fss != null) {
                for (int i=0; i<fss.length; i++) {
                    mSavedState.add((Fragment.SavedState)fss[i]);
                }
            }
            Iterable<String> keys = bundle.keySet();
            for (String key: keys) {
                if (key.startsWith("f")) {
                    int index = Integer.parseInt(key.substring(1));
                    Fragment f = mFragmentManager.getFragment(bundle, key);
                    if (f != null) {
                        while (mFragments.size() <= index) {
                            mFragments.add(null);
                        }
                        f.setMenuVisibility(false);
                        mFragments.set(index, f);
                    } else {
                        Log.w(TAG, "Bad fragment at key " + key);
                    }
                }
            }
        }
    }
}

Then I implemented ViewPager.OnPageChangeListener in the fragment that contains the viewpager. And in the void onPageScrollStateChanged(int state) method I updated the view of my fragments in viewpager.

public class WordDetailFragment extends Fragment implements ViewPager.OnPageChangeListener {

    @BindView(R.id.word_parts_tab_layout)
    TabLayout mTabLayout;
    @BindView(R.id.word_part_view_pager)
    ViewPager mViewPager;

    private WordPagerAdapter mPagerAdapter;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_word_detail, container, false);
        ButterKnife.bind(this, view);
        mPagerAdapter = new WordPagerAdapter(getChildFragmentManager(), getActivity());
        mViewPager.setAdapter(mPagerAdapter);
        mTabLayout.setupWithViewPager(mViewPager);
        mViewPager.addOnPageChangeListener(this);
        return view;
    }

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

    }

    Fragment mFragment;

    @Override
    public void onPageSelected(int position) {
        mFragment = mPagerAdapter.getItem(position);
    }

    @Override
    public void onPageScrollStateChanged(int state) {
        if (state == ViewPager.SCROLL_STATE_IDLE) {
            if (mFragment instanceof WordDefinitionFragment) {
                ((WordDefinitionFragment) mFragment).showTranslation();
               //Log.e("StateChanged", mFragment.getClass().getSimpleName());
            }
            if (mFragment instanceof WordExampleFragment) {
                ((WordExampleFragment) mFragment).showTranslation();
               //Log.e("StateChanged", mFragment.getClass().getSimpleName());
            }
            if (mFragment instanceof WordColFamFragment) {
                ((WordColFamFragment) mFragment).showTranslation();
                //Log.e("StateChanged", mFragment.getClass().getSimpleName());
            }
        }
    }


}

And in my fragments, I had a callback interface which was implemented in the activity. And in void setUserVisibleHint(boolean isVisibleToUser) method I updated the UI again because neighbor fragments did not get updated.

public class WordExampleFragment extends Fragment {

    @BindView(R.id.example_field_name_id)
    TextView mExampleFieldNameTV;
    @BindView(R.id.word_tv)
    TextView mWordTv;
    @BindView(R.id.play_sound_iv)
    ImageView mPlaySoundIv;
    @BindView(R.id.show_translate_switch)
    Switch mShowTranslateSwitch;

    private TextView mExample1ContentFaTV;

    private List<WordInfo> mWordInfoList;
    private List<Example> mExampleList;
    private int mWordNumber;

    //callback interface
    private TranslateModeChangeListener mWordPartFragments;

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        try {
            mWordPartFragments = (TranslateModeChangeListener) context;
        } catch (ClassCastException ex) {
            throw new ClassCastException("Must implement WordPartFragments interface");
        }
    }


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_word_example, container, false);
        ButterKnife.bind(this, view);

        mWordInfoList = WordActivity.sWordInfoList;
        mWordNumber = WordActivity.sWordNumber;
        mExampleList = mWordInfoList.get(mWordNumber).getExamples(); 

        showTranslation();
        mShowTranslateSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
               //this method is called from activity=> callback interface
               mWordPartFragments.changeTranslateMode(b);
                showTranslation();
            }
        });
        return view;
    }

    //this method is called when viewpager shows this fragment and it is visible to user
    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);

        if (isVisibleToUser) {
            showTranslation();
        }
    }

    //this methoud updates my UI: shows or hides the translation
    public void showTranslation() {
        if (isAdded()) {
            boolean showTranslation = WordActivity.sTranslationShown;
            //Log.e("Ex", "added & shown = " + showTranslation);
            mShowTranslateSwitch.setChecked(showTranslation);
            mExample1ContentFaTV.setVisibility((showTranslation) ? View.VISIBLE : View.INVISIBLE);

        }
    }
}