I want to create app with dynamic add/remove tabs option and I've got problem with one thing.
When I'm adding tabs first time everything is ok but when I remove one tab and I want to add new tab again then there is no onCreateView called on that new fragment.
Example:
I add new 3 tabs:
Add tab 1:
ViewPagerAdapter vpa = (ViewPagerAdapter) mViewPager.getAdapter();
vpa.addTab("TAB 1");
vpa.notifyDataSetChanged();
onCreateView is called.
some logs:
[add tab] mViewPager.getChildCount() = 1
[add tab] vpa.getCount() = 1
Add tab 2:
vpa.addTab("TAB 2");
vpa.notifyDataSetChanged();
onCreateView is called.
some logs:
[add tab] mViewPager.getChildCount() = 2
[add tab] vpa.getCount() = 2
Add tab 3:
vpa.addTab("TAB 3");
vpa.notifyDataSetChanged();
onCreateView is called.
some logs:
[add tab] mViewPager.getChildCount() = 3
[add tab] vpa.getCount() = 3
now I want to delete Tab 2:
vpa.removeTab("TAB 2");
vpa.notifyDataSetChanged();
some logs:
[add tab] mViewPager.getChildCount() = 2
[add tab] vpa.getCount() = 2
and I want to add Tab 2 again:
vpa.addTab("TAB 2");
vpa.notifyDataSetChanged();
and there is no onCreateView called :(
some logs:
[add tab] mViewPager.getChildCount() = 2
[add tab] vpa.getCount() = 3
Can anyone know how to fix that? Why onCreateView is not always called?
App code:
private void initViewpagerAndTabs(){
mViewPager = (ViewPager) findViewById(R.id.viewpager_slave);
mViewPager.setOffscreenPageLimit(10);
mAdapterViewPager = new ViewPagerAdapter(getSupportFragmentManager());
mViewPager.setAdapter(mAdapterViewPager);
mTabLayout = (TabLayout) findViewById(R.id.tabLayout_slave);
mTabLayout.setupWithViewPager(mViewPager);
}
/** Tabs content fragment */
public static class DummyFragment extends Fragment implements RecyclerAdapter.ViewHolder.ClickListener {
private RecyclerView mRecyclerView;
private RecyclerAdapter mAdapterRecycler;
private RecyclerView.LayoutManager mLayoutManager;
private Fab fab;
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.content_slave, container, false);
//setRetainInstance(true);
initRecyclerView(view);
fab = (Fab) getActivity().findViewById(R.id.fabRemoveItems);
return view;
}
private void initRecyclerView(View view){
mRecyclerView = (RecyclerView) view.findViewById(R.id.recycler_view_slave);
// use this setting to improve performance if you know that changes
// in content do not change the layout size of the RecyclerView
mRecyclerView.setHasFixedSize(true);
mLayoutManager = new LinearLayoutManager(getContext());
mRecyclerView.setLayoutManager(mLayoutManager);
RecyclerView.ItemAnimator itemAnimator = mRecyclerView.getItemAnimator();
itemAnimator.setAddDuration(500);
itemAnimator.setRemoveDuration(500);
mAdapterRecycler = new RecyclerAdapter(this);
mRecyclerView.setAdapter(mAdapterRecycler);
}
}
private class ViewPagerAdapter extends FragmentStatePagerAdapter {
private final List<DummyFragment> mFragmentList = new ArrayList<>();
private final List<String> mFragmentTitleList = new ArrayList<>();
public ViewPagerAdapter(FragmentManager fm) {
super(fm);
}
public DummyFragment getItem(int num) throws IndexOutOfBoundsException{
return mFragmentList.get(num);
}
public void addTab(String title) throws Exception {
if(isTabExist(title)){
throw new Exception("Tabs already exist!");
}
//Log.d(TAG, "[addTab] mFragmentTitleList before add: " + mFragmentTitleList);
//Log.d(TAG, "[addTab] mFragmentList before add: " + mFragmentList);
mFragmentList.add(new DummyFragment());
mFragmentTitleList.add(title);
//Log.d(TAG, "[addTab] mFragmentTitleList after add: " + mFragmentTitleList);
//Log.d(TAG, "[addTab] mFragmentList after add: " + mFragmentList);
}
public void removeTab(int position){
//Log.d(TAG, "[removeTab] mFragmentTitleList before removed: " + mFragmentTitleList);
//Log.d(TAG, "[removeTab] mFragmentList before removed: " + mFragmentList);
mFragmentTitleList.remove(position);
mFragmentList.remove(position);
//Log.d(TAG, "[removeTab] mFragmentTitleList after removed: " + mFragmentTitleList);
//Log.d(TAG, "[removeTab] mFragmentList after removed: " + mFragmentList);
}
private boolean isTabExist(String name){
for(int i = 0; i < mFragmentTitleList.size(); i++) {
if (mFragmentTitleList.get(i).equals(name)) {
return true;
}
}
return false;
}
public int getTabPosition(String tabName) throws IndexOutOfBoundsException{
for(int i = 0; i < mFragmentTitleList.size(); i++) {
if (mFragmentTitleList.get(i).equals(tabName)) {
return i;
}
}
throw new IndexOutOfBoundsException();
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
super.destroyItem(container, position, object);
getSupportFragmentManager().beginTransaction().remove((Fragment) object)
.commit();
}
#Override
public int getItemPosition(Object object) {
if (mFragmentList.contains(object)) return mFragmentList.indexOf(object);
else return POSITION_NONE;
}
#Override
public int getCount() {
return mFragmentList.size();
}
#Override
public CharSequence getPageTitle(int position) {
return mFragmentTitleList.get(position);
}
}
Ok, I resolve that problem.
Delete:
mViewPager.setOffscreenPageLimit(10);
Override methods to save fragments state:
public void onViewStateRestored(#Nullable Bundle savedInstanceState)
public void onSaveInstanceState(Bundle outState)
And I've created my own FragmentStatePagerAdapter class. I needed to don't save fragment state on tab which was deleted.
Code:
public class FragStatePagerAdaper extends PagerAdapter {
private static final String TAG = "FragStatePagerAdapter";
private static final boolean DEBUG = true;
private final FragmentManager mFragmentManager;
private FragmentTransaction mCurTransaction = null;
private ArrayList<Fragment.SavedState> mSavedState = new ArrayList<Fragment.SavedState>();
/** Cached fragments */
private ArrayList<Fragment> mFragments = new ArrayList<Fragment>();
private Fragment mCurrentPrimaryItem = null;
private List<DummyFragment> mFragmentList = new ArrayList<>();
private List<String> mFragmentTitleList = new ArrayList<>();
public FragStatePagerAdaper(FragmentManager fm) {
mFragmentManager = fm;
}
/**
* Return the Fragment associated with a specified position.
*/
public DummyFragment getItem(int num) throws IndexOutOfBoundsException{
return mFragmentList.get(num);
}
#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);
if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
if (mSavedState.size() > position) {
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);
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());
while (mSavedState.size() <= position) {
mSavedState.add(null);
}
mSavedState.set(position, fragment.isAdded()
? mFragmentManager.saveFragmentInstanceState(fragment) : null);
mFragments.set(position, null);
mCurTransaction.remove(fragment);
}
public void addTab(String title) throws Exception {
if(isTabExist(title)){
throw new Exception("Tabs already exist!");
}
mFragmentList.add(new DummyFragment());
mFragmentTitleList.add(title);
}
//look here!
public void removeTab(int position){
Log.d(TAG, "removeTab: position = " + position);
mFragmentTitleList.remove(position);
mFragmentList.remove(position);
mSavedState.remove(position); // <---- here!
}
private boolean isTabExist(String name){
for(int i = 0; i < mFragmentTitleList.size(); i++) {
if (mFragmentTitleList.get(i).equals(name)) {
return true;
}
}
return false;
}
public int getTabPosition(String tabName) throws IndexOutOfBoundsException{
for(int i = 0; i < mFragmentTitleList.size(); i++) {
if (mFragmentTitleList.get(i).equals(tabName)) {
return i;
}
}
throw new IndexOutOfBoundsException();
}
#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();
}
ArrayList<Fragment> update = new ArrayList<Fragment>();
for (int i=0, n=mFragments.size(); i < n; i++) {
Fragment f = mFragments.get(i);
if (f == null) continue;
int pos = getItemPosition(f);
while (update.size() <= pos) {
update.add(null);
}
update.set(pos, f);
}
mFragments = update;
}
#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);
}
for (int i=0; i<mFragments.size(); i++) {
Fragment f = mFragments.get(i);
if (f != null && f.isAdded()) {
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();
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);
}
}
}
}
}
#Override
public int getItemPosition(Object object) {
if (mFragmentList.contains(object)) return mFragmentList.indexOf(object);
else return POSITION_NONE;
}
#Override
public int getCount() {
return mFragmentList.size();
}
#Override
public CharSequence getPageTitle(int position) {
return mFragmentTitleList.get(position);
}
}
Greetings!
Related
I am trying to implement ViewPager with multiple Fragment and using static newInstance to instantiate the fragments inside a loop from activity
for(String name : list) {
viewPagerAdapter.addFragment(ProductListFragment.newInstance(name), name);
}
public static ProductListFragment newInstance(String category) {
ProductListFragment fragment = new ProductListFragment();
ProductListFragment.category = category;
Bundle args = new Bundle();
args.putString(CATEGORY_PARAM, category);
fragment.setArguments(args);
return fragment;
}
As the fragment's life cycle are getting called only after loop ends, i am not able to retain the values of parameter i am sending.
For your better understanding, think that there will be three tabs and in each tab i will show products which are related to "name" value and i want to filter those products using "name" value from a productList in fragment. As soon as first fragments lifecycle get calls i will be having the last value of "name" as i have mentioned fragments lifecycle starts only after loop ends which means i will be filtering products only related to last value of "name" and same products will be shown in all three tabs. How to resolve this?
Do not try to use ViewPagerAdapter.addFragment(), fragments should be created in getItem() method.
If you need a dynamic FragmentPagerAdapter, then you can do:
public class DynamicFragmentPagerAdapter extends PagerAdapter {
private static final String TAG = "DynamicFragmentPagerAdapter";
private final FragmentManager fragmentManager;
public static abstract class FragmentIdentifier implements Parcelable {
private final String fragmentTag;
private final Bundle args;
public FragmentIdentifier(#NonNull String fragmentTag, #Nullable Bundle args) {
this.fragmentTag = fragmentTag;
this.args = args;
}
protected FragmentIdentifier(Parcel in) {
fragmentTag = in.readString();
args = in.readBundle(getClass().getClassLoader());
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(fragmentTag);
dest.writeBundle(args);
}
protected final Fragment newFragment() {
Fragment fragment = createFragment();
Bundle oldArgs = fragment.getArguments();
Bundle newArgs = new Bundle();
if(oldArgs != null) {
newArgs.putAll(oldArgs);
}
if(args != null) {
newArgs.putAll(args);
}
fragment.setArguments(newArgs);
return fragment;
}
protected abstract Fragment createFragment();
}
private ArrayList<FragmentIdentifier> fragmentIdentifiers = new ArrayList<>();
private FragmentTransaction currentTransaction = null;
private Fragment currentPrimaryItem = null;
public DynamicFragmentPagerAdapter(FragmentManager fragmentManager) {
this.fragmentManager = fragmentManager;
}
private int findIndexIfAdded(FragmentIdentifier fragmentIdentifier) {
for (int i = 0, size = fragmentIdentifiers.size(); i < size; i++) {
FragmentIdentifier identifier = fragmentIdentifiers.get(i);
if (identifier.fragmentTag.equals(fragmentIdentifier.fragmentTag)) {
return i;
}
}
return -1;
}
public void addFragment(FragmentIdentifier fragmentIdentifier) {
if (findIndexIfAdded(fragmentIdentifier) < 0) {
fragmentIdentifiers.add(fragmentIdentifier);
notifyDataSetChanged();
}
}
public void removeFragment(FragmentIdentifier fragmentIdentifier) {
int index = findIndexIfAdded(fragmentIdentifier);
if (index >= 0) {
fragmentIdentifiers.remove(index);
notifyDataSetChanged();
}
}
#Override
public int getCount() {
return fragmentIdentifiers.size();
}
#Override
public void startUpdate(#NonNull ViewGroup container) {
if (container.getId() == View.NO_ID) {
throw new IllegalStateException("ViewPager with adapter " + this
+ " requires a view id");
}
}
#SuppressWarnings("ReferenceEquality")
#NonNull
#Override
public Object instantiateItem(#NonNull ViewGroup container, int position) {
if (currentTransaction == null) {
currentTransaction = fragmentManager.beginTransaction();
}
final FragmentIdentifier fragmentIdentifier = fragmentIdentifiers.get(position);
// Do we already have this fragment?
final String name = fragmentIdentifier.fragmentTag;
Fragment fragment = fragmentManager.findFragmentByTag(name);
if (fragment != null) {
currentTransaction.attach(fragment);
} else {
fragment = fragmentIdentifier.newFragment();
currentTransaction.add(container.getId(), fragment, fragmentIdentifier.fragmentTag);
}
if (fragment != currentPrimaryItem) {
fragment.setMenuVisibility(false);
fragment.setUserVisibleHint(false);
}
return fragment;
}
#Override
public void destroyItem(#NonNull ViewGroup container, int position, #NonNull Object object) {
if (currentTransaction == null) {
currentTransaction = fragmentManager.beginTransaction();
}
currentTransaction.detach((Fragment) object);
}
#SuppressWarnings("ReferenceEquality")
#Override
public void setPrimaryItem(#NonNull ViewGroup container, int position, #NonNull Object object) {
Fragment fragment = (Fragment) object;
if (fragment != currentPrimaryItem) {
if (currentPrimaryItem != null) {
currentPrimaryItem.setMenuVisibility(false);
currentPrimaryItem.setUserVisibleHint(false);
}
fragment.setMenuVisibility(true);
fragment.setUserVisibleHint(true);
currentPrimaryItem = fragment;
}
}
#Override
public void finishUpdate(#NonNull ViewGroup container) {
if (currentTransaction != null) {
currentTransaction.commitNowAllowingStateLoss();
currentTransaction = null;
}
}
#Override
public boolean isViewFromObject(#NonNull View view, #NonNull Object object) {
return ((Fragment) object).getView() == view;
}
#Override
public Parcelable saveState() {
Bundle bundle = new Bundle();
bundle.putParcelableArrayList("fragmentIdentifiers", fragmentIdentifiers);
return bundle;
}
#Override
public void restoreState(Parcelable state, ClassLoader loader) {
Bundle bundle = ((Bundle)state);
bundle.setClassLoader(loader);
fragmentIdentifiers = bundle.getParcelableArrayList("fragmentIdentifiers");
}
}
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
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);
}
}
}
I am using TabLayout with ViewPager (fragmented webview) to get a tabbed browser. The issue I am facing is my app crashes when I remove any tab expect the first and the last tab from tabbed layout.
In this case we are also removing linked Fragment from ViewPager which contains the WebView.
After that again we add multiple tabs and removes then the app crash.
The Add function
private void addViewPagerAndTab(String tag) {
mTabTags.add(tag);
MyFragment browserWebviewFragment = new MyFragment();
Bundle bundle = new Bundle();
String timestamp = System.currentTimeMillis() + "";
bundle.putString("timestamp", timestamp);
browserWebviewFragment.setArguments(bundle);
adapter.addFrag(browserWebviewFragment, "TAB " + countTabHeading, "TAB " + countTabHeading, timestamp);
countTabHeading++;
if (adapter != null) {
adapter.notifyDataSetChanged();
}
tabLayout.setupWithViewPager(viewPager);
tabLayout.setScrollPosition(((mTabTags.size()) + 1), 0f, true);
viewPager.setCurrentItem(((mTabTags.size()) + 1));
setupTabIcons();
}
The Remove Function
private void removeTabs() {
int position = tabLayout.getSelectedTabPosition();
if (deleteTabPosition > position) {
mTabTags.remove(deleteTabPosition);
tabLayout.removeTabAt(deleteTabPosition);
adapter.removeViewPagerView(deleteTabPosition);
} else if (deleteTabPosition < position) {
mTabTags.remove(deleteTabPosition);
tabLayout.removeTabAt(deleteTabPosition);
adapter.removeViewPagerView(deleteTabPosition);
tabLayout.setScrollPosition(position - 1, 0f, true);
viewPager.setCurrentItem(position - 1);
} else {
int tabCount = tabLayout.getTabCount();
if (tabCount > position + 1) {
mTabTags.remove(deleteTabPosition);
tabLayout.removeTabAt(deleteTabPosition);
adapter.removeViewPagerView(deleteTabPosition);
tabLayout.setScrollPosition(position, 0f, true);
viewPager.setCurrentItem(position);
} else if (tabCount <= position + 1) {
mTabTags.remove(deleteTabPosition);
tabLayout.removeTabAt(deleteTabPosition);
adapter.removeViewPagerView(deleteTabPosition);
tabLayout.setScrollPosition(position - 1, 0f, true);
viewPager.setCurrentItem(position - 1);
}
}
}
The ViewPager Adapter
private final List<Fragment> mFragmentList = new ArrayList<>();
private final List<TabInfo> mFragmentTitleList = new ArrayList<TabInfo>();
private int deleteTabPosition = -1;
class ViewPagerAdapter extends FragmentStatePagerAdapter {
public ViewPagerAdapter(FragmentManager manager) {
super(manager);
}
#Override
public Fragment getItem(int position) {
return mFragmentList.get(position);
}
#Override
public int getCount() {
return mFragmentList.size();
}
public void addFrag(Fragment fragment, String title, String url, String timestamp) {
mFragmentList.add(fragment);
TabInfo infoTab = new TabInfo();
infoTab.setStrTabName(title);
infoTab.setStrTabUrl(url);
infoTab.setTimestamp(timestamp);
mFragmentTitleList.add(infoTab);
}
#Override
public int getItemPosition(Object object) {
Fragment fragmentTemp = (Fragment) object;
if (mFragmentList.contains(fragmentTemp)) {
return mFragmentList.indexOf((Fragment) object);
} else {
return POSITION_NONE;
}
}
#Override
public CharSequence getPageTitle(int position) {
return mFragmentTitleList.get(position).getStrTabName();
}
public View getTabView(final int position) {
View v = LayoutInflater.from(MainActivity.this).inflate(R.layout.tab_bg, null);
TextView tv = (TextView) v.findViewById(R.id.tvTab);
tv.setText(mFragmentTitleList.get(position).getStrTabName());
final ImageView img = (ImageView) v.findViewById(R.id.ic_tab_close);
img.setImageResource(R.drawable.ic_tab_close);
img.setTag(mFragmentTitleList.get(position).getTimestamp());
img.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
String strTag = (String) v.getTag();
deleteTabPosition = -1;
if (mFragmentTitleList != null) {
for (int i = 0; i < mFragmentTitleList.size(); ++i) {
if (mFragmentTitleList.get(i).getTimestamp().equals(strTag)) {
deleteTabPosition = i;
break;
}
}
}
if (tabLayout.getTabCount() != 1) {
removeTabs();
} else {
}
}
});
return v;
}
/*
* Remove the View pager page associated with Tab
* */
public void removeViewPagerView(int position) {
mFragmentList.remove(position);
mFragmentTitleList.remove(position);
this.notifyDataSetChanged();
}
}
Full sourcecode on following link
http://45.33.27.221/CustomTabNoida.zip
java.lang.IllegalStateException: Fragment already active
at android.support.v4.app.Fragment.setInitialSavedState(Fragment.java:574)
at android.support.v4.app.FragmentStatePagerAdapter.instantiateItem(FragmentStatePagerAdapter.java:110)
at android.support.v4.view.ViewPager.addNewItem(ViewPager.java:870)
at android.support.v4.view.ViewPager.populate(ViewPager.java:1054)
at android.support.v4.view.ViewPager.setCurrentItemInternal(ViewPager.java:552)
at android.support.v4.view.ViewPager.setCurrentItemInternal(ViewPager.java:514)
at android.support.v4.view.ViewPager.dataSetChanged(ViewPager.java:946)
at android.support.v4.view.ViewPager$PagerObserver.onChanged(ViewPager.java:2910)
at android.database.DataSetObservable.notifyChanged(DataSetObservable.java:37)
at android.support.v4.view.PagerAdapter.notifyDataSetChanged(PagerAdapter.java:276)
at com.sourcefuse.sns.views.Activities.HomeScreenActivity.addViewPagerAndTab(HomeScreenActivity.java:603)
at com.sourcefuse.sns.views.Activities.HomeScreenActivity.onClick(HomeScreenActivity.java:525)
at android.view.View.performClick(View.java:5204)
at android.view.View$PerformClick.run(View.java:21153)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5417)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
I've got a TabLayout in my Activity, it shows one fragment when it's created, but I need to be able to add a new fragment to it after completing an operation (or create both when the activity is created, but show only the first, and then show the second).
I've tried adding the fragment to the viewPagerAdapter and then calling the notifyDataSetChanged() method, the frament it's added, but it's title doesn't show up in the tabLayout and if I try to slide to it, I get an IndexOutOfBoundsException: Invalid index 1, size is 1
Does anyone got some advice to give me?
My activity, where I add the first fragmen:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.act_one);
// Crete a new fragment
FragmentOne fragmentOne = new FragmentoOne();
tabLayout = (TabLayout) findViewById(R.id.tabLayout);
viewPager = (ViewPager) findViewById(R.id.viewPager);
// Creates the viewPagerAdapter
viewPagerAdapter = new ViewPagerAdapter(getSupportFragmentManager());
// Adds the fragment to ViewPagerAdapter
viewPagerAdapter.addFragment(fragmentoOne, "First"); // this line can cause crashes
viewPager.setAdapter(viewPagerAdapter);
tabLayout.setupWithViewPager(viewPager);
}
And here is where I try to add the new fragment:
public void onAddFragment2() {
viewPagerAdapter.addFragment(new FragmentoTwo(), "Second"); // new FragmentoTwo() should be in FragmentPagerAdapter.getItem()
viewPagerAdapter.notifyDataSetChanged();
}
set the adapter again after you added it with setAdapter
This is important, if you have 3 fragment you shout write 3 in your Adapter.
#Override
public int getCount() {
return 3;
}
To modify the contents of a FragmentPagerAdapter, you need to use the following code:
public class DynamicFragmentPagerAdapter extends PagerAdapter {
private static final String TAG = "DynamicFragmentPagerAdapter";
private final FragmentManager fragmentManager;
public static abstract class FragmentIdentifier implements Parcelable {
private final String fragmentTag;
private final Bundle args;
public FragmentIdentifier(#NonNull String fragmentTag, #Nullable Bundle args) {
this.fragmentTag = fragmentTag;
this.args = args;
}
protected FragmentIdentifier(Parcel in) {
fragmentTag = in.readString();
args = in.readBundle(getClass().getClassLoader());
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(fragmentTag);
dest.writeBundle(args);
}
protected final Fragment newFragment() {
Fragment fragment = createFragment();
Bundle oldArgs = fragment.getArguments();
Bundle newArgs = new Bundle();
if(oldArgs != null) {
newArgs.putAll(oldArgs);
}
if(args != null) {
newArgs.putAll(args);
}
fragment.setArguments(newArgs);
return fragment;
}
protected abstract Fragment createFragment();
}
private ArrayList<FragmentIdentifier> fragmentIdentifiers = new ArrayList<>();
private FragmentTransaction currentTransaction = null;
private Fragment currentPrimaryItem = null;
public DynamicFragmentPagerAdapter(FragmentManager fragmentManager) {
this.fragmentManager = fragmentManager;
}
private int findIndexIfAdded(FragmentIdentifier fragmentIdentifier) {
for (int i = 0, size = fragmentIdentifiers.size(); i < size; i++) {
FragmentIdentifier identifier = fragmentIdentifiers.get(i);
if (identifier.fragmentTag.equals(fragmentIdentifier.fragmentTag)) {
return i;
}
}
return -1;
}
public void addFragment(FragmentIdentifier fragmentIdentifier) {
if (findIndexIfAdded(fragmentIdentifier) < 0) {
fragmentIdentifiers.add(fragmentIdentifier);
notifyDataSetChanged();
}
}
public void removeFragment(FragmentIdentifier fragmentIdentifier) {
int index = findIndexIfAdded(fragmentIdentifier);
if (index >= 0) {
fragmentIdentifiers.remove(index);
notifyDataSetChanged();
}
}
#Override
public int getCount() {
return fragmentIdentifiers.size();
}
#Override
public void startUpdate(#NonNull ViewGroup container) {
if (container.getId() == View.NO_ID) {
throw new IllegalStateException("ViewPager with adapter " + this
+ " requires a view id");
}
}
#SuppressWarnings("ReferenceEquality")
#NonNull
#Override
public Object instantiateItem(#NonNull ViewGroup container, int position) {
if (currentTransaction == null) {
currentTransaction = fragmentManager.beginTransaction();
}
final FragmentIdentifier fragmentIdentifier = fragmentIdentifiers.get(position);
// Do we already have this fragment?
final String name = fragmentIdentifier.fragmentTag;
Fragment fragment = fragmentManager.findFragmentByTag(name);
if (fragment != null) {
currentTransaction.attach(fragment);
} else {
fragment = fragmentIdentifier.newFragment();
currentTransaction.add(container.getId(), fragment, fragmentIdentifier.fragmentTag);
}
if (fragment != currentPrimaryItem) {
fragment.setMenuVisibility(false);
fragment.setUserVisibleHint(false);
}
return fragment;
}
#Override
public void destroyItem(#NonNull ViewGroup container, int position, #NonNull Object object) {
if (currentTransaction == null) {
currentTransaction = fragmentManager.beginTransaction();
}
currentTransaction.detach((Fragment) object);
}
#SuppressWarnings("ReferenceEquality")
#Override
public void setPrimaryItem(#NonNull ViewGroup container, int position, #NonNull Object object) {
Fragment fragment = (Fragment) object;
if (fragment != currentPrimaryItem) {
if (currentPrimaryItem != null) {
currentPrimaryItem.setMenuVisibility(false);
currentPrimaryItem.setUserVisibleHint(false);
}
fragment.setMenuVisibility(true);
fragment.setUserVisibleHint(true);
currentPrimaryItem = fragment;
}
}
#Override
public void finishUpdate(#NonNull ViewGroup container) {
if (currentTransaction != null) {
currentTransaction.commitNowAllowingStateLoss();
currentTransaction = null;
}
}
#Override
public boolean isViewFromObject(#NonNull View view, #NonNull Object object) {
return ((Fragment) object).getView() == view;
}
#Override
public Parcelable saveState() {
Bundle bundle = new Bundle();
bundle.putParcelableArrayList("fragmentIdentifiers", fragmentIdentifiers);
return bundle;
}
#Override
public void restoreState(Parcelable state, ClassLoader loader) {
Bundle bundle = ((Bundle)state);
bundle.setClassLoader(loader);
fragmentIdentifiers = bundle.getParcelableArrayList("fragmentIdentifiers");
}
}
Then generally, to make a modification actually happen (because ViewPagers are quirky), you'll most likely need:
viewPager.setAdapter(null);
viewPager.setAdapter(adapter);
What I'm trying to achieve:
An Activity with a ViewPager that displays Fragments for a list of Objects in the Adapter (FragmentStatePagerAdapter).
Initially the Activity loads N (lets say 5) objects from the SQLite DB into the Adapter. These objects are chosen with some randomness.
When the user is reaching the end of the list, the activity shall load M (let M be 3) more objects from the DB, add them to the adapter and call notifyDataSetChanged(). When adding them, I check if the new Objects already exist in the list and if they do, the pre-existing one gets removed and the loaded one gets added to the list's tail.
Thus, I'm trying to achieve something like an infinite scrolling ViewPager (NOT a "circular" ViewPager as I want new Objects to be fetched constantly, instead of going back to the begging of the list).
I have some working code and included a sample for the pattern I'm following down bellow. However, I keep getting this exception and have no idea why:
IllegalStateException: Fragment MyObjectFragment{id...} is not currently in the FragmentManager
at android.app.FragmentManagerImpl.saveFragmentInstanceState(FragmentManager.java:553)
at android.support.v13.app.FragmentStatePagerAdapter.destroyItem(FragmentStatePagerAdapter.java:140)
at android.support.v4.ViewPager.populate(ViewPager.java:1002)
...
Code Sample:
The Activity:
public class MyActivitty extends FragmentActivity {
public MyPagerAdapter adapter;
public ViewPager pager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.my_acyivity_layout);
pager = (ViewPager) findViewById(R.id.viewPager);
ArrayList<MyObject> myObjects = new ArrayList<MyObject>();
// loadInitialObjectsFromDB(int N) goes to SQLite DB and loads the N first objects to show on the ViewPager...
myObjects = loadInitialObjectsFromDB(5);
// Adapter will use the previously fetched objects
adapter = new MyPagerAdapter(this, getFragmentManager(), myObjects);
pager.setAdapter(adapter);
pager.setOffscreenPageLimit(2);
// (...)
}
// (...)
}
The PagerAdapter:
public class MyPagerAdapter extends FragmentStatePagerAdapter implements
ViewPager.OnPageChangeListener {
private MyActivity context;
private ArrayList<MyObject> objectList;
private int currentPosition = 0;
// (...)
public MyPagerAdapter(MyActivity context, FragmentManager fragmentManager, ArrayList<MyObject> objects)
{
super(fragmentManager);
this.context = context;
this.objectList = objects;
}
#Override
public Fragment getItem(int position)
{
MyObject object = objectList.get(position);
return MyObjectFragment.newInstance(position, object);
}
#Override
public int getItemPosition(Object object){
MyObjectFragment frag = (MyObjectFragment) object;
MyObject object = frag.getMyObject();
for(int i = 0; i < objectList.size(); i++)
{
if(objectList.get(i).getId() == object.getId())
{
return i;
}
}
return PagerAdapter.POSITION_NONE;
}
#Override
public int getCount()
{
return objectList.size();
}
#Override
public void onPageSelected(int position)
{
currentPosition = position;
}
// (...)
}
#Override
public void onPageScrollStateChanged(int state)
{
switch(state)
{
case ViewPager.SCROLL_STATE_DRAGGING:
// (...)
break;
case ViewPager.SCROLL_STATE_IDLE:
// if we are reaching the "end" of the list (while scrolling to the right), load more objects
if(currentPosition <= position && position >= answerList.size()-1)
{
// Function in MyActivity that fetches N more objects.
// and adds them to this adapter's ArrayList<MyObject> objectList
// it checks for duplicates so that if the fetched object was already in the back of the list, it is shown again
context.getMoreObjects(3);
notifyDataSetChanged();
}
getMoreQuestions(currentPosition);
case ViewPager.SCROLL_STATE_SETTLING:
break;
}
}
My Fragment:
public class MyObjectFragment extends Fragment {
// Object to represent
private MyObject object;
public static Fragment newInstance(MyActivity context,
int position, MyObject object) {
MyObjectFragment frag = new MyObjectFragment();
Bundle args = new Bundle();
args.putParcelable("Object", object);
frag.setArguments(args);
return frag;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
this.currentInflater = inflater;
final View rootView = inflater.inflate(
R.layout.fragment_my_object, container, false);
// get object from Bundle, set UI, events, etc...
}
// (...)
}
Any idea on why am I getting this Exception? It seems like the FragmentStatePagerAdapter is trying to destroy an item that no longer exists, but I don't understand why.
EDIT 1:
If I comment my #Override getItemPosition(Object object), I don't get the exception anymore. However, I need to override getItemPosition because there is a use case in which the user deletes the currently shown Object causing it to disappear from the adapter's array and forcing the getItemPosition to return POSITION_NONE if the item doesn't exist anymore.
EDIT 2:
Now I do know that this exception only happens when I remove items from my adapter's objectList. I have two situations where MyObject instances are deleted from the objectList:
When the getMoreObjects() adds fetches an object from the DB that was already in the objectList, I delete it and re-add it to the head of the list. I do this to avoid having objects with the same Id in the objectList, as their Id is used by the getItemPosition() to know if they exist and their position.
Before returning, getMoreObjects(), removes the N first objects from the list. I do know that the FragmentStatePagerAdapter already saves memory by only keeping in memory fragments for some of the objects, but I still would like to avoid growing my objectList too much. For now, I have this line commented, as it's not that important.
Solved with the help of this question which itself points at this issue.
FragmentStatePagerAdapter caches the fragments and their saved states in two ArrayLists: mFragments and mSavedState. But when the fragments' order changes (as could happen in my case), there's no mechanism for reordering the elements of mFragments and mSavedState. Therefore, the adapter will provide the wrong fragments to the pager.
I've adapted the code provided in that and changed the import from app.support.v4.Fragment to android.app.Fragment.
public abstract class MyFragmentStatePagerAdapter extends PagerAdapter {
private static final String TAG = "FragmentStatePagerAdapter";
private static final boolean DEBUG = true;
private final FragmentManager mFragmentManager;
private FragmentTransaction mCurTransaction = null;
private long[] mItemIds = new long[] {};
private ArrayList<Fragment.SavedState> mSavedState = new ArrayList<Fragment.SavedState>();
private ArrayList<Fragment> mFragments = new ArrayList<Fragment>();
private Fragment mCurrentPrimaryItem = null;
public MyFragmentStatePagerAdapter(FragmentManager fm) {
mFragmentManager = fm;
mItemIds = new long[getCount()];
for (int i = 0; i < mItemIds.length; i++) {
mItemIds[i] = getItemId(i);
}
}
/**
* Return the Fragment associated with a specified position.
*/
public abstract Fragment getItem(int position);
/**
* Return a unique identifier for the item at the given position.
*/
public int getItemId(int position) {
return position;
}
#Override
public void notifyDataSetChanged() {
long[] newItemIds = new long[getCount()];
for (int i = 0; i < newItemIds.length; i++) {
newItemIds[i] = getItemId(i);
}
if (!Arrays.equals(mItemIds, newItemIds)) {
ArrayList<Fragment.SavedState> newSavedState = new ArrayList<Fragment.SavedState>();
ArrayList<Fragment> newFragments = new ArrayList<Fragment>();
for (int oldPosition = 0; oldPosition < mItemIds.length; oldPosition++) {
int newPosition = POSITION_NONE;
for (int i = 0; i < newItemIds.length; i++) {
if (mItemIds[oldPosition] == newItemIds[i]) {
newPosition = i;
break;
}
}
if (newPosition >= 0) {
if (oldPosition < mSavedState.size()) {
Fragment.SavedState savedState = mSavedState.get(oldPosition);
if (savedState != null) {
while (newSavedState.size() <= newPosition) {
newSavedState.add(null);
}
newSavedState.set(newPosition, savedState);
}
}
if (oldPosition < mFragments.size()) {
Fragment fragment = mFragments.get(oldPosition);
if (fragment != null) {
while (newFragments.size() <= newPosition) {
newFragments.add(null);
}
newFragments.set(newPosition, fragment);
}
}
}
}
mItemIds = newItemIds;
mSavedState = newSavedState;
mFragments = newFragments;
}
super.notifyDataSetChanged();
}
#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);
if (DEBUG) Log.v(TAG, "Adding item #" + position + ": f=" + fragment);
if (mSavedState.size() > position) {
Fragment.SavedState fss = mSavedState.get(position);
if (fss != null) {
fragment.setInitialSavedState(fss);
}
}
while (mFragments.size() <= position) {
mFragments.add(null);
}
fragment.setMenuVisibility(false);
mFragments.set(position, fragment);
mCurTransaction.add(container.getId(), fragment);
return fragment;
}
public void destroyItemState(int position) {
mFragments.remove(position);
mSavedState.remove(position);
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
Fragment fragment = (Fragment) object;
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
//position = getItemPosition(object);
if (DEBUG) Log.v(TAG, "Removing item #" + position + ": f=" + object
+ " v=" + ((Fragment)object).getView());
if (position >= 0) {
while (mSavedState.size() <= position) {
mSavedState.add(null);
}
mSavedState.set(position, mFragmentManager.saveFragmentInstanceState(fragment));
if(position < mFragments.size()){
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);
}
if (fragment != null) {
fragment.setMenuVisibility(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 = new Bundle();
if (mItemIds.length > 0) {
state.putLongArray("itemids", mItemIds);
}
if (mSavedState.size() > 0) {
Fragment.SavedState[] fss = new Fragment.SavedState[mSavedState.size()];
mSavedState.toArray(fss);
state.putParcelableArray("states", fss);
}
for (int i=0; i<mFragments.size(); i++) {
Fragment f = mFragments.get(i);
if (f != null) {
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);
mItemIds = bundle.getLongArray("itemids");
if (mItemIds == null) {
mItemIds = new long[] {};
}
Parcelable[] fss = bundle.getParcelableArray("states");
mSavedState.clear();
mFragments.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);
}
}
}
}
}
}
Credit for the original code goes to user #UgglyNoodle.
Then, instead of using FragmentStatePagerAdapter I use the MyFragmentStatePagerAdapter from above and override getItemPosition() and getItemId() consistently with getItem().