I have 6 pages in my ViewPager
and OffscreenPageLimit=2
(easy to reproduce my bug).
all data in the 6 pages are from server. In onCreateView
, I send a request to server, and refresh UI when I get data from server.
When I select first tab and change to last quickly several times, some pagers display wrong. And at that time my field mMainLayout
in fragment is not null.
For example, I have a ListView
in my first page. When the page is wrong, another ListView is on the top of the right ListView. When I try to scroll ListView, only the right one(bottom one) moved.
I know that my response listener holds reference of mMainLayout and some other views, I create a new mMainLayout in method onCreateView, I thought fragment use new mMainLayout when it re-attach/restore and drop the old one(or remove it from the container). But I was wrong.
I know that FragmentPagerAdapter
attach/re-attach fragment in instantiateItem
and detach in destroyItem
. Adapter didn't remove a fragment. Adapter didn't make a new fragment. Fragment keep the view and view states itself.
I remove mMainLayout from container and set all views in my fragment null at onDestroyView
and save nothing in onSaveInstanceState
. But still easy to reproduce the bug.
activity:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
registerReceiver(receiver, new IntentFilter("com.souyidai.intent.action.log_fragment"));
FragmentManager fragmentManager = getFragmentManager();
mResources = getResources();
mMainPagerAdapter = new MainPagerAdapter(fragmentManager);
mPager = (ViewPager) findViewById(R.id.pager);
mPager.setAdapter(mMainPagerAdapter);
mPager.setOffscreenPageLimit(CACHE_SIZE);
mIndicator = (TabPageIndicator) findViewById(R.id.indicator);
mIndicator.setViewPager(mPager);
...
}
private class MainPagerAdapter extends FragmentPagerAdapter {
public MainPagerAdapter(FragmentManager fm) {
super(fm);
}
@Override
public Fragment getItem(int position) {
MainConfig.TabItem tab = mTabs.get(position);
String tabType = tab.getTabType();
String code = tab.getCode();
MainConfig.TabItem.SubTabItem subTabItem = tab.getSubitem().get(0);
Fragment fragment = MainFragment.newInstance(code, subTabItem);
return fragment;
}
@Override
public CharSequence getPageTitle(int position) {
MainConfig.TabItem tab = mTabs.get(position);
return tab.getTitle();
}
@Override
public int getCount() {
return mTabs.size();
}
}
fragment:
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
mContainer = container;
mMainLayout = inflater.inflate(R.layout.fragment_main, container, false);
...
}
@Override
public void onDestroyView() {
super.onDestroyView();
mContainer.removeView(mMainLayout);
mContainer = null;
mMainLayout = null;
mSwipeRefreshLayout = null;
mListView = null;
mHeaderLayout = null;
mFooterLayout = null;
...
}
android.support.v13.app.FragmentPagerAdapter
@Override
public Object instantiateItem(ViewGroup container, int position) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
final long itemId = getItemId(position);
// Do we already have this fragment?
String name = makeFragmentName(container.getId(), itemId);
Fragment fragment = mFragmentManager.findFragmentByTag(name);
if (fragment != null) {
if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
mCurTransaction.attach(fragment);
} else {
fragment = getItem(position);
if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
mCurTransaction.add(container.getId(), fragment,
makeFragmentName(container.getId(), itemId));
}
if (fragment != mCurrentPrimaryItem) {
FragmentCompat.setMenuVisibility(fragment, false);
FragmentCompat.setUserVisibleHint(fragment, false);
}
return fragment;
}
I find this:
Android fragment onCreateView creating duplicate views on top of each other
But I only create new fragment in FragmentPagerAdapter.getItem
. So we're different.
I solve this by removing all children of mMainLayout if mMainLayout is not null in method onCreateView. Then everything is fine. But I am still confused as to why this bug happens?
I did some tests.
1. try to add a fragment twice, app crash and log says: java.lang.IllegalStateException: Fragment already added: ...
2. try to attach a fragment twice, system does not call Fragment.onCreate
or Fragment.onCreateView
This situation occurred due to custom implementation of instantiateItem()
One thing I have noticed. You are messing check if fragment is already added.
@Override
public Object instantiateItem(ViewGroup container, int position) {
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
}
final long itemId = getItemId(position);
// Do we already have this fragment?
String name = makeFragmentName(container.getId(), itemId);
Fragment fragment = mFragmentManager.findFragmentByTag(name);
if (fragment != null) {
if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
//make sure you are not attaching already added fragment
//try with comment and uncomment two lines below
//if(!fragment.isAdded())
//mCurTransaction.attach(fragment);
} else {
fragment = getItem(position);
if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
mCurTransaction.add(container.getId(), fragment,
makeFragmentName(container.getId(), itemId));
}
if (fragment != mCurrentPrimaryItem) {
FragmentCompat.setMenuVisibility(fragment, false);
FragmentCompat.setUserVisibleHint(fragment, false);
}
return fragment;
}
Don't know why you are creating class member field for mMainLayout
//try to go with default
View view = inflater.inflate(R.layout.fragment_main, container, false);
recommend you to study this
You can try with code, I was facing similar issue got resolved with bellow changes. In that case no need to make null all the references in onDestroyView
. as in that caseyou need to always make null check before using these reference anywhere else in the code.
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if(mMainLayout == null)
{
mMainLayout = inflater.inflate(R.layout.fragment_main, container,false);
...
}
return mMainLayout;
}
When mMainlayout
is not null, it mean that your fragment instance has already one instance of the mMainLayout
and already added to ViewGroup container
no need to add it again. You are facing issue as you are adding same view again to same container.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With