Duplicate Fragments with RecyclerView appearing in ViewPager after swiping - android

Fragment with RecyclerView is having problem inside ViewPager. Duplicate RecyclerView appears overlapped when the fragments gets recreated after swiping.
I am using ViewPager, Fragment, FragmentStatePagerAdapter
Pic 1 : For the first time there RecyclerView is fine.
Pic 2 : After swiping multiple fragments and then swiping back to same fragment then duplicate fragments gets overlapped while scrolling.
using FragmentStatePagerAdapter
class SlidingFragmentPagerAdapter extends FragmentStatePagerAdapter {
SparseArray<FragmentMain> registeredFragments = new SparseArray<FragmentMain>();
FragmentManager fm;
public SlidingFragmentPagerAdapter(FragmentManager fm) {
super(fm);
this.fm = fm;
}
#Override
public Fragment getItem(int position) {
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DATE, -1 * (FragmentViewPager.MAX_PAGE - position - 1));
return FragmentMain.newInstance(position + "", cal.getTime());
}
#Override
public int getCount() {
return MAX_PAGE;
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
FragmentMain fragment = (FragmentMain) super.instantiateItem(container, position);
registeredFragments.put(position, fragment);
return fragment;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
registeredFragments.remove(position);
super.destroyItem(container, position, object);
}
public FragmentMain getRegisteredFragment(int position) {
return registeredFragments.get(position);
}
}
Using ViewPager
mViewPager = (ViewPager) view.findViewById(R.id.viewpager);
rootView.findViewById(R.id.img_floating_btn2);
mSlidingTabLayout = (SlidingTabLayout) view.findViewById(R.id.sliding_tabs);
mSlidingTabLayout.setDistributeEvenly(true);
adapter = new SlidingFragmentPagerAdapter(getFragmentManager());
mViewPager.setAdapter(adapter);
mViewPager.setCurrentItem(MAX_PAGE);
mSlidingTabLayout.setViewPager(mViewPager);
Note
I tried using mViewPager.setOffscreenPageLimit(adapter.getCount()); buti have more than 50 fragments in ViewPager

I had the same problem, I had a ViewPager with a RootFragment containing other fragments. I added these fragments to the RootFragment with:
getFragmentManager().beginTransaction().add(containerLayout.getId(), ItemFragment.newInstance()).commit();
I fixed it by changing it to replace instead of add.
getFragmentManager().beginTransaction().replace(containerLayout.getId(), ItemFragment.newInstance()).commit();
Even when the container is empty you have to use replace instead of add!

Related

Android FragmentPagerAdapter can't change tag of fragment

I am developing a simple web beowser with dynamic tabs to allow user to add/remove them. I use FragmentPagerAdapter, ViewPager and TabLayout to do this. If I create 2 tabs (add 2 fragments to list) and then remove the second one, everything work well and I can add another fragment to list. But if I add 2 and then remove the firs one and try to add new fragment to list, I recieve exception:
java.lang.IllegalStateException: Can't change tag of fragment Page{162e3786 #1 id=0x7f0d007d android:switcher:2131558525:1}: was android:switcher:2131558525:1 now android:switcher:2131558525:0
I think thats because each fragment has unique id inside FragmentPagerAdapter. 2 fragments will have id's 0 and 1. If I remove the first item(id is 0) and then add new one(id will be 1). But item with id 1 is already exists! Is there a way to change id's programmatically or set id's when creating fragments? Thanks in advance!
My FragmentPagerAdapter:
public class PagerAdapter extends FragmentPagerAdapter {
private List<Page> fragments;
public PagerAdapter(FragmentManager fm, List<Page> pages) {
super(fm);
this.fragments = pages;
}
#Override
public Fragment getItem(int position) {
return this.fragments.get(position);
}
#Override
public int getCount() {
return this.fragments.size();
}
#Override
public int getItemPosition(Object object) {
int index = pages.indexOf(object);
if (index == -1){
return PagerAdapter.POSITION_NONE;
} else {
return index;
}
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
FragmentManager manager = ((Fragment) object).getFragmentManager();
FragmentTransaction trans = manager.beginTransaction();
trans.remove((Fragment) object);
trans.commit();
}
}
Finally, after a hours of search and tries I found the solution.
To fix that you need to override getItemId() method of FragmentPagerAdapter in which you have to return a unique id for each fragment.

How can i set a tag for viewpager fragments?

I,m using slidingTabLayout with 4 tabs and a viewpager and 4 fragment class for every tab.
i need to reload one of my fragments (first tab) in my project when user click some button, and for this i need to have a tag for that fragment
my question is : how can i set a tag for one of viewpagers fragment???
My MainActivity tab and viewpager codes:
public class MainActivity extends AppCompatActivity
implements NavigationView.OnNavigationItemSelectedListener {
Toolbar toolbar;
ViewPager pager;
ViewPagerAdapter adapter;
SlidingTabLayout tabs;
CharSequence Titles[]={"نوع","قیمت","برند","همه"};
int Numboftabs = 4;
#Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(CalligraphyContextWrapper.wrap(newBase));
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Snackbar.make(view, "درج آگهی رایگان", Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
}
});
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
navigationView.setNavigationItemSelectedListener(this);
// Creating The ViewPagerAdapter and Passing Fragment Manager, Titles fot the Tabs and Number Of Tabs.
adapter = new ViewPagerAdapter(getSupportFragmentManager(),Titles,Numboftabs);
// Assigning ViewPager View and setting the adapter
pager = (ViewPager) findViewById(R.id.pager);
pager.setAdapter(adapter);
// Assiging the Sliding Tab Layout View
tabs = (SlidingTabLayout) findViewById(R.id.tabs);
tabs.setDistributeEvenly(true); // To make the Tabs Fixed set this true, This makes the tabs Space Evenly in Available width
// Setting Custom Color for the Scroll bar indicator of the Tab View
tabs.setCustomTabColorizer(new SlidingTabLayout.TabColorizer() {
#Override
public int getIndicatorColor(int position) {
return getResources().getColor(R.color.colorWhite);
}
});
// Setting the ViewPager For the SlidingTabsLayout
tabs.setViewPager(pager);
pager.setCurrentItem(3);
} and ....
Thank you a lot
There are couple of solution for this.
1). When the FragmentPagerAdapter adds a Fragment to the FragmentManager, it uses a special tag based on the particular position that the Fragment will be placed. So you can simply get the current visible Fragment in your ViewPager using these lines of code
Fragment fragment = getSupportFragmentManager().findFragmentByTag("android:switcher:" + R.id.pager + ":" + ViewPager.getCurrentItem());
// based on the current position you can then cast the page to the correct Fragment class and call some method inside that fragment to reload the data:
if (0 == ViewPager.getCurrentItem() && null != fragment) {
((TabFragment1)fragment).reloadFragmentData();
}
2). You should keep track of all the active fragments in the FragmentStatePagerAdapter. For demo sample you can refer to second approach here
3). You can make use of EventBus to post events to your Fragment from anywhere. All you have to do is to register your Fragment for that specific event. For official documentation refer this
Here is the method which is used by FragmentPagerAdapter to create the Fragment's tag name:
private static String makeFragmentName(int viewId, long id) {
return "android:switcher:" + viewId + ":" + id;
}
viewId is the id of the ViewPager.
id is the position of the fragment inside the ViewPager.
Example:
String fragmentTag = makeFragmentName(myViewPager.getId(), 2);
Fragment fragment = getSupportFragmentManager().findFragmentByTag(fragmentTag);
In case anyone's using FragmentStateAdapter, the tag is a bit different.
This is the code from FragmentStateAdapter:
mFragmentManager.beginTransaction()
.add(fragment, "f" + holder.getItemId())
.setMaxLifecycle(fragment, STARTED)
.commitNow();
As you can see it's a combination of static string "f" and getItemId().
After overriding fun getItemId(position: Int): Long in your adapter, finding a specific fragment should be quite simple to do. For example, override fun
getItemId(position: Int): Long {
return if(position == 0) {
"my_first_fragment"
}else "my_second_fragment"
}
The tag for our first fragment would be "fmy_first_fragment" and found with childFragmentManager.findFragmentByTag("fmy_first_fragment") as? MyFirstFragment
Careful whenever you look for fragment, you have to use the same FragmentManager as was used to create it. So I recommend creating your adapter with FragmentManager and LifeCycle as a constructor.
Though this question has already been answered. I have another approach for achiving this and this might be better one as the way fragments are tagged in the code are subjected to change. Also to point out using adapter.getItem(position) does not return the instance of fragment being displayed, it will depend on our own implementation of getItem method.
So Basically the problem is that we do not have the instance of Fragments that are present in the viewPager. The idea is to override method
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;
}
of FragmentStatePagerAdapter. If we look at the default implementation of this method, it basically does the job of instantiating new Fragments if they do not exists and returning them. Also it stores them in a list of fragments which is private. So what we can do is Override this method and store the instances on fragments in our own list. This can be implemented in the following way
private class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
private ArrayList<Fragment> fragmentList;
private final int NUM_PAGES;
public ScreenSlidePagerAdapter(FragmentManager fm, int numPages) {
super(fm);
NUM_PAGES = numPages;
fragmentList = new ArrayList<>(NUM_PAGES);
}
#Override
public Fragment getItem(int position) {
Fragment fragment = new CustomFragment();
//this method can be modified according to the requirement
return fragment;
}
#Override
public int getCount() {
return NUM_PAGES;
}
public Fragment getFragment(int position){
return fragmentList.get(position);
}
#Override
public Object instantiateItem(ViewGroup container, int position){
Fragment fragment = (Fragment) super.instantiateItem(container,position);
if(fragmentList.size()<NUM_PAGES&&!fragmentList.contains(fragment)){
fragmentList.add(fragment);
}
return fragment;
}
}
Therefore we will have the instances of all the fragments available with us we would no longer need any tags to find the fragment instances being displayed to the user.

How to update Fragments in ViewPager after updating Adapter?

I call loadCategory method from different items of navigation drawer.
On the first call ViewPager load the correct data. But on the second call data doesn't change. ViewPager shows the old data, Please help.
Thanks in advance.
private void loadCategory(int id) {
toolbar.setTitle(categoryList[id]);
adapter = new PagerAdapter(getSupportFragmentManager());
mViewPager.setAdapter(adapter);
adapter.notifyDataSetChanged();
}
public class PagerAdapter extends FragmentPagerAdapter{
public PagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
super.destroyItem(container, position, object);
}
#Override
public Fragment getItem(int position) {
Bundle bundle = new Bundle();
bundle.putString("shayari",currentShayaaris[position]);
Fragment SFragment = new ShayariFragment();
SFragment.setArguments(bundle);
return SFragment;
}
#Override
public int getCount() {
return currentShayaaris.length;
}
}
If you are inside a fragment you should instantiate adapter like this
adapter = new PagerAdapter(getChildFragmentManager());
There are several ways to update
When using FragmentPagerAdapter or FragmentStatePagerAdapter,
if you want to switch out the actual fragments that are being
displayed, you need to avoid FragmentPagerAdapter and use
FragmentStatePagerAdapter.
check this link for more information.Update ViewPager
Override getItemPosition in your PagerAdapter
public int getItemPosition(Object object) {
return POSITION_NONE;
}
second way to update viewpager
You might need to removeView and addView to reload your viewpager to get new changes in your loadCategory function after updating adapter.
ViewGroup parent = (ViewGroup) mViewPager.getParent();
if (parent != null) {
parent.removeView(mViewPager);
mViewPager.getAdapter().notifyDataSetChanged();
parent.addView(mViewPager);
}

android - FragmentStatePagerAdapter fragment not visible

I have implemented view pager using the FragmentStatePagerAdapter. I have used 3 fragments in the view. The view pager is showing all 3 tabs with views as it should be , now my issue is that the third fragment is not visible but i am able to scroll till third tab and when i scroll back i see the second fragment but again the first fragment vanishes and all i am left is with second fragment.
mPager = (ViewPager)mContext.findViewById(R.id.viewPager);
mPager.setOnPageChangeListener(this);
mPagerAdapter = new MyPagerAdapter(mContext.getSupportFragmentManager(),mContext);
mPager.setAdapter(mPagerAdapter);
adapter code is
public class MyPagerAdapter extends FragmentStatePagerAdapter {
private int NUM_PAGES = 3;
List<Fragment> listFragment = new ArrayList<Fragment>();
private MyActivity mContext;
public MyPagerAdapter(FragmentManager fragmentManager, MyActivity mActivity) {
super(fragmentManager);
this.mContext = mActivity;
listFragment.add(new FirstFragment());
listFragment.add(new SecondFragment());
listFragment.add(new ThirdFragment());
}
#Override
public int getItemPosition(Object object){
return PagerAdapter.POSITION_NONE;
}
#Override
public Fragment getItem(int position) {
Log.v("fragment position",""+position);
return listFragment.get(position);
}
#Override
public int getCount() {
return NUM_PAGES;
}
}
I am stuck in this for a while, i would really appreciate some help.Thank you.
It is advised to use FragmentPagerAdapter instead of FragmentStatePagerAdapter, because of very nature of those two. FragmentStatePagerAdapter is being that is able to scroll through infinite views. In case of fixed amount of them, like your case you should use FragmentPagerAdapter.
If you happen to have context issues later override following method:
#Override
public Object instantiateItem(ViewGroup container, int position) {
Fragment fragment = (Fragment) super.instantiateItem(container, position);
mFragments.remove(position);
mFragments.add(position, fragment);
return fragment;
}
Where mFragments is your list.

Tabs + ViewPager + FragmentStatePagerAdapter - How to remove fragment?

I'm using the "Tabs + Swipe" project and I'm having a difficult time removing a fragment.
Steps I'm doing:
Remove tab from database
Remove tab from the FragmentStatePagerAdapter data source
Remove tab from the actionBar.
Remove fragment using Support FragmentManager.
The problem:
After I perform the remove, for some reason, I can still scroll to the right and see an empty fragment. I can't select it, it just bounces back.
It seems like the fragment is not being removed but rather changes its position to the tag from the left.
My Adapter:
public class SectionsPagerAdapter extends FragmentStatePagerAdapter {
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
Fragment fragment = new TabFragment();
Bundle args = new Bundle();
args.putInt(TabFragment.ARG_TAB_POSITION, position);
fragment.setArguments(args);
return fragment;
}
#Override
public int getCount() {
return tabsList.size();
}
#Override
public CharSequence getPageTitle(int position) {
return tabsList.get(position).getTitle();
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
super.destroyItem(container, position, object);
FragmentManager manager = ((Fragment)object).getFragmentManager();
android.support.v4.app.FragmentTransaction trans = manager.beginTransaction();
trans.remove((Fragment)object);
trans.commit();
}
}
My remove method (called from within the fragment):
public void removeTab() {
mTabTableHandler.deleteTab(tab.getId()); //db
tabsList.remove(tabPosition); //data source
actionBar.removeTabAt(tabPosition); // actionbar
getActivity().getSupportFragmentManager().beginTransaction().remove(this).commit(); // support fragmentmanager
tabsList = mTabTableHandler.query(); //requery db
mSectionsPagerAdapter.notifyDataSetChanged(); //notify adapter
}
Appreciate the help!
you can find the answer here -
Remove Fragment Page from ViewPager in Android
it seems what missing in your code is overwriting of the following adapter's method:
#Override
public int getItemPosition(Object object){
return PagerAdapter.POSITION_NONE;
}
explanation why such "weird" solution works you can find in the answer to the question in the link I provided you.
public void removeAllFragments(ViewGroup container) {
/*
* keep this line because section pager adapter has
* the bug to not delete the last fragment completely
* -> so we add an empty fragment
*/
int iStart = 0;
addFragment(new Fragment());
Fragment frag;
int size = fragments.size();
for (int iPos = iStart; iPos < size; iPos++) {
frag = getItem(iStart);
if (frag.isAdded()) {
destroyItem(container, iStart, frag);
}
fragments.remove(frag);
notifyDataSetChanged();
}
}

Categories

Resources