FragmentPagerAdapter strange behavior - android

So i have the next problem with FragmentPagerAdapter. I have a TabLayout with 3 tabs representing 3 fragments that i can switch. So when i switch to third fragment, for some reason the first one disappears (or its view). Does anyone know how to fix this problem? Thanks in advance.
public class SectionsPagerAdapter extends FragmentPagerAdapter {
private static final int FRAGMENT_COUNT = 3;
private final List<Fragment> listOfFragments = new ArrayList<>();
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
return listOfFragments.get(position);
}
#Override
public int getCount() {
return FRAGMENT_COUNT;
}
public void addFragment(Fragment fragment) {
listOfFragments.add(fragment);
}
}
This is the code for FragmentPagerAdapter.

And i think i fixed this :) Only thing i did is to override the FragmentPagerAdapter destroyItem() method, with empty body (no super).

I don't know what is your problem. but if in your app only 3 fragments are there than you can try setOffscreenPageLimit method of ViewPager
ViewPager.setOffscreenPageLimit(3);

One of the problems here is a slightly confusing API.
In FragmentPagerAdapter, getItem(int position) actually means "create item". In other words, you shouldn't try to manually cache the Fragments inside a list in your Adapter.
#Override
public Fragment getItem(int position) {
return listOfFragments.get(position); //No! don't do this
}
Even though it may see counter-intuitive, you should actually create a new instance of the Fragment you want inside getItem, sticking very closely to the official Google example:
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return FirstFragment.getInstance();
case 1:
return SecondFragment.getInstance();
}
}
Otherwise you will run into problems where the FragmentManager's cache and your own List<Fragment> cache are out-of-sync. In short, caching of Fragments is handled by the FragmentManager and you don't need to roll-your-own caching.

I don't know really what happen but this is my code, hope this help you
public class AdapterFragmentViewPager extends FragmentPagerAdapter {
private List<Fragment> fragmentList = new ArrayList<>();
public AdapterFragmentViewPager(FragmentManager fm) {
super(fm);
}
public void addFragment(Fragment fragment) {
fragmentList.add(fragment);
}
#Override
public Fragment getItem(int position) {
return fragmentList.get(position);
}
#Override
public int getCount() {
return fragmentList.size();
}
}
and this is my Tablayout:
private AdapterFragmentViewPager adapterFragmentViewPager = new AdapterFragmentViewPager(getSupportFragmentManager());
TabLayout.Tab home = tabLayout.newTab();
tabLayout.addTab(home);
TabLayout.Tab newest = tabLayout.newTab();
tabLayout.addTab(newest);
TabLayout.Tab author = tabLayout.newTab();
tabLayout.addTab(author);
TabLayout.Tab category = tabLayout.newTab();
tabLayout.addTab(category);
TabLayout.Tab saved = tabLayout.newTab();
tabLayout.addTab(saved);
adapterFragmentViewPager.addFragment(FragmentHome.newInstance());
adapterFragmentViewPager.addFragment(FragmentQuote.newInstance());
adapterFragmentViewPager.addFragment(FragmentAuthor.newInstance());
adapterFragmentViewPager.addFragment(FragmentCategory.newInstance());
adapterFragmentViewPager.addFragment(FragmentFavorites.newInstance());
viewPager.setAdapter(adapterFragmentViewPager);
tabLayout.setupWithViewPager(viewPager);

Related

Tabbed fragments list views are not updated when related data is changed in other fragment

I used a TabLayout for a tabbed view in my app. I added three List fragments - one for each tab.
The content of either of the fragments is supposed to be changed (and the list views have to be updated) when new record is added/removed from any of the fragments.
But after I modify some data and swiped to the next tab the list view is not updated and retained the old data.
I created an interface that is implemented in all of the ListFragments as follows:
public interface UpdatableFragment {
void updateContent();
}
public class LogFragment implements UpdatableFragment{
...
#Override
public void updateContent() {
Activity activity = getActivity();
if (isAdded() && activity != null) {
getLoaderManager().restartLoader(LOG_LIST_LOADER, null, this);
}
}
...
}
And I call the method in the MainActivity tab select listener
tabLayout.setOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
#Override
public void onTabSelected(TabLayout.Tab tab) {
if(adapter != null){
int position = viewPager.getCurrentItem();
viewPager = (ViewPager) findViewById(R.id.viewpager);
viewPager.setCurrentItem(position);
Fragment tabbedFragment = adapter.getItem(viewPager.getCurrentItem());
if(tabbedFragment instanceof UpdatableFragment){
((UpdatableFragment) tabbedFragment).updateContent();
}
}
}
But the condition if (isAdded() && activity != null) in updateContent() method sometimes returns false because activity becomes null. On other times, it returns the MainActivity instance and the loader restarts.
Why is this inconsistent behavior happening and how can I make the ListFragments always show fresh content when their corresponding tabs are selected?
EDIT
adding the fragment pager code
class ViewPagerAdapter extends FragmentPagerAdapter {
private final List<Fragment> mFragmentList = new ArrayList<>();
private final List<String> mFragmentTitleList = new ArrayList<>();
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 addFragment(Fragment fragment, String title) {
mFragmentList.add(fragment);
mFragmentTitleList.add(title);
}
#Override
public CharSequence getPageTitle(int position) {
return mFragmentTitleList.get(position);
}
}
The getItem(int position) method does not return a reference to the Fragment currently in that position of your ViewPager. The getItem() method is used in the PagerAdapter when it calls instantiateItem() to create your views/fragments, so when you call getItem(int position) it's creating a brand new Fragment for you, and this is not added/attached to your Activity (causing the false/null in your if check). If you look into the code for FragmentPagerAdapter.instantiateItem() you can see where it calls getItem() and then adds the Fragment with the FragmentManager.
One way to get around this is to cache your Fragment references in your FragmentPagerAdapter (do this by caching the Fragment returned by instantiateItem() and remove the references in adapter.destroyItem()), or calling adapter.notifyDataSetChanged() to re-create your views with the updated data.
UPDATE:
You want to add something like this to your FragmentPagerAdapter:
public class Adapter extends FragmentPagerAdapter {
private SparseArray<Fragment> fragmentCache = new SparseArray<>();
#Override public Object instantiateItem(ViewGroup container, int position) {
Fragment fragment = (Fragment) super.instantiateItem(container, position);
fragmentCache.put(position, fragment);
return fragment;
}
#Override public void destroyItem(ViewGroup container, int position, Object object) {
fragmentCache.remove(position);
super.destroyItem(container, position, object);
}
// other code....
}
You can then get references to all the fragments currently in your adapter from the fragmentCache, and then you can call your updateContent method on them.

How to update fragments dynamically?

I am using Tab layout with view pager and adding fragments dynamically. Now I need to update a TextView which present in ViewPagers fragment, according to swipe of fragment or click of tab. Suppose if I click on first tab or swipe my fragments then TextView in OperatorPlanList fragment is updtate with text like "I just click".
Note - TextView is present in layout of fragment.
When I try to find position by using position in get view method, FragmentStatePageAdapter, then I found out it update position twice when first or last fragment is seen.
So how can I update my fragment dynamically according to current fragment seen on screen in ViewPager???
This is my FragmentStatePagerAdapter adapter.
class ViewPagerFinder extends FragmentStatePagerAdapter {
private final List<Fragment> mFragmentList = new ArrayList<>();
private final List<String> mFragmentTitleList = new ArrayList<>();
public ViewPagerFinder(FragmentManager manager) {
super(manager);
}
#Override
public Fragment getItem(int position){
return mFragmentList.get(position);
}
#Override
public int getCount() {
System.out.println("getcount>>>" + mFragmentList.size());
return mFragmentList.size();
}
public void addFragment(Fragment fragment, String title) {
mFragmentList.add(fragment);
mFragmentTitleList.add(title);
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
return super.instantiateItem(container, position);
}
#Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
super.setPrimaryItem(container, position, object);
}
#Override
public CharSequence getPageTitle(int position) {
return mFragmentTitleList.get(position);
}
}
This is how I am setting up ViewPager.
private void setupViewPager(ViewPager viewPager) {
adapter = new ViewPagerFinder(getSupportFragmentManager());
for(int i = 0; i<numberOfPlans;i++){
adapter.addFragment(new OperatorPlanList(),"Plan");
//This is how I am setting up my fragments.
viewPager.setAdapter(adapter);
}
}
This is how I am setting up my Tabs.
private void setupTabIcons() {
System.out.println("number of plans ="+numberOfPlans);
for(int i = 0; i<numberOfPlans;i++){
HashMap<String,String> map = applist.get(i);
String planName = map.get("plan_name");
TextView tabOne = (TextView) LayoutInflater.from(this).inflate(R.layout.custom_tab, null);
tabOne.setText(planName);
tabLayout.getTabAt(i).setCustomView(tabOne);
}
}
This is how I am calling my methods.
viewPager = (ViewPager)findViewById(R.id.viewpager);
setupViewPager(viewPager);
tabLayout = (TabLayout) findViewById(R.id.tabs);
tabLayout.setupWithViewPager(viewPager);
setupTabIcons();
Create an interface that all of the tab fragments will implement
public interface TabFragment {
void onTabSwiped(int position);
}
In the class that hosts the ViewPager have it implement ViewPager.OnPageChangeListener
Inside your ViewPagerFinder, during the instantiateItem() keep a list of TabFragment's (Create the fragment and cast it to the new interface, THEN call super).
Now in your Class that implements OnPageChangeListener, call the method onTabSwiped on all of your TabFragments passing in the position inside the overridden method onPageSelected(int position).
Now you will get a callback to each fragment during swiping, so you can check if its the correct position and perform whatever you need to do if it is the correct page.
There are ways to make this more efficient, but it's just a quick way to get it working. It will all be similar
-- You could also just get the fragment from your list inside your adapter during onPageSelected(int position), then call the interface method.
#Override
public void onPageSelected(int position){
pagerAdapter.mFragmentList.get(position).onTabSwiped();
}

How to add Fragment to first position in FragmentPagerAdapter

I am using FragmentPagerAdapter and a ViewPager to add custom Fragments EDIT: from my MainActivity (also sending a bunch of extra data based on a JSON response via bundle) and using swiping motions to move to the next Fragments in the List.
public class MyPagerAdapter extends FragmentPagerAdapter implements Serializable {
public List<Fragment> fragments;
public FragmentManager fm;
public MyPagerAdapter(FragmentManager fm) {
super(fm);
this.fm = fm;
this.fragments = new ArrayList<Fragment>();
}
#Override
public Fragment getItem(int position) {
return fragments.get(position);
}
#Override
public int getCount() {
return fragments.size();
}
}
Everything is working fine as long as I'm adding new Fragments by
using
MyPagerAdapter pageAdapter = new MyPagerAdapter(getSupportFragmentManager());
pager = (ViewPager)findViewById(R.id.myViewPager);
pageAdapter.fragments.add(new CustomFragment());
pager.setAdapter(pageAdapter);
But I can't find a proper way to add Fragments to the beginning of the List and swipe back.
I've tried both
pageAdapter.fragments.add(0, new CustomFragment());
as well as changing the FragmentPagerAdapters List to LinkedList and using
pageAdapter.fragments.addFirst(new CustomFragment());
and then refreshing the adapter by using
pageAdapter.notifyDataSetChanged();
and i keep getting the following exception:
java.lang.IllegalStateException: Can't change tag of fragment CustomFragment{2ead9520 #10 id=0x7f0a0002 android:switcher:2131361794:10}: was android:switcher:2131361794:10 now android:switcher:2131361794:11
The key methods no one has talked about yet is public int getItemPosition(Object) which is used to remap fragments to pages after they move and public long getItemId(int position) which must be overridden by a pager adapter that reorders fragments. The default implementation uses the position of the page as the id, so reordering confuses the FragmentPagerAdapter.
(I am leaving out the Serializable interface as it is irrelevant for the purposes of answering the question - How to reorder fragments in a FragmentPagerAdapter).
public class MyPagerAdapter extends FragmentPagerAdapter {
public List<Fragment> fragments;
public FragmentManager fm;
public MyPagerAdapter(FragmentManager fm) {
super(fm);
this.fm = fm;
this.fragments = new ArrayList<Fragment>();
}
void addFragmentAtPosition(int position, Fragment f) {
if(position == fragments.size())
fragments.add(f);
else
fragments.add(position, f);
notifyDataSetChanged();
}
void removeFragmentAtPosition(int position) {
Fragment f = fragments.remove(position);
if(f != null)
fm.beginTransaction().remove(f).commit();
notifyDataSetChanged();
}
#Override
public Fragment getItem(int position) {
return fragments.get(position);
}
#Override
public int getCount() {
return fragments.size();
}
#Override
public int getItemPosition(Object object){
/*
* called when the fragments are reordered to get the
* changes.
*/
int idx = fragments.indexOf(object);
return idx < 0 ? POSITION_NONE : idx;
}
#Override
public long getItemId(int position) {
/*
* map to a position independent ID, because this
* adapter reorders fragments
*/
return System.identityHashCode(fragments.get(position));
}
}
The key additions are the overrides of public int getItemPosition(Object) and public long getItemId(int). These allow the FragmentPagerAdapter to reposition the existing fragments and to identify the existing active fragments in the FragmentManager cache correctly.
You should not create and add fragments this way. Instead just instantiate the fragments in getItem and the adapter will take care of using them. just do this:
#Override
public Fragment getItem(int position) {
Fragment fragment = new CustomFragment();
fragments.add(fragment)
return fragment
}
I would suggest you don't keep a list of references to fragments since it is not necessary and you risk to create memory leaks.
What i would do is create the fragment only when required like this :
#Override
public Fragment getItem(int position) {
Fragment fragment = new MyFragment();
return fragment;
}
To solve your problem you should create the fragment based on your needs, for example if you have fragments of different class instances like for example one instance of MyFragment another one of YourFragment and so on, just keep a list which says which kind of fragment occupy that position.
For example:
myListMap = new HashMap<Integer, Integer>();
myListMap.put(position, type);
and then create the fragment on the fly:
#Override
public Fragment getItem(int position) {
Fragment fragment = null;
int type = ...find fragment type in that position ....
if(type == MYFRAGMENTTYPE) {
fragment = new MyFragment();
}
return fragment;
}
Don't know if you still need the answer, but I was trying to do something similar and just found the solution :)
You are receiving that exception because of your getItem function. You are returning the fragment in the position that the function receives, and this position is always the last position of the array because that would correspond to the last added fragment in a "typical" usage.
In your case, you want to add a new Fragment in the first position, so your getItem will return twice the same fragment and throw the exception.
To avoid this you need to create a public function into your Adapter and the index of the fragment you are adding, and then return this specific fragment.
PS.: I'm developing only in Kotlin for about 6 months now, so it can have some typos.
public int newFragmentIndex = 0;
public List<Fragment> fragments;
...
public void addFragmentAt(int index, Fragment fragment) {
newFragmentIndex = index;
pageAdapter.fragments.add(index, fragment);
notifyDataSetChanged();
}
public void addFragment(Fragment fragment) {
newFragmentIndex = fragments.size();
pageAdapter.fragments.add(fragment);
notifyDataSetChanged();
}
...
#Override
public Fragment getItem(int position) {
return fragments.get(newFragmentIndex);
}
To call this from your Activity, change your pageAdapter.fragments.add(new CustomFragment());
to
pageAdapter.fragments.addFragment(new CustomFragment());
And
pageAdapter.fragments.add(0, new CustomFragment());
to
pageAdapter.fragments.addFragmentAt(0, new CustomFragment());
Hope this helps you with your problem!

Android fragmentpageradapter update data

Here , I explain my problem. I install a ViewPager and FragmentPagerAdapter so you can slide between two page.
Each fragment contains data ( TextView ) .
When creating my ViewPager with the data passed as a parameter everything goes well.
But if I want to change the data (ie the content of the fragment) it does not refresh
public class MaPagerAdapter extends FragmentPagerAdapter {
private Meteo[]lameteo;
public MaPagerAdapter(FragmentManager fm, Meteo[]lameteo) {
super(fm);
this.lameteo = lameteo;
Log.i("aa", "ville maPagerAdapter"+lameteo[0].getLoc());
}
#Override
public Fragment getItem(int position) {
switch(position) {
case 0: return page_droite.newInstance(lameteo[0]);
case 1: return page_gauche.newInstance(lameteo);
}
return null;
}
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
public void setData(Meteo[] meteo){
this.lameteo=meteo;
}
#Override
public int getCount() {
return 2;
}
}
And my main
if(mPagerAdapter!=null){ //if pagerAdapter already initialized
mPagerAdapter.setData(meteo);
mPagerAdapter.notifyDataSetChanged();
}else
mPagerAdapter = new MaPagerAdapter(getSupportFragmentManager(), meteo);
ViewPager pager = (ViewPager) findViewById(R.id.viewpager);
pager.setAdapter(mPagerAdapter);
Can you help me please ?
No, the pager adapter doesn't work that way. Android doesn't realize that you have that particular data in your fragments, so it wouldn't know how to update it.
You'll have to get references to your fragments inside your setData call, and then you'll have to write new methods inside your fragments that you can call to reset their data. Inside the fragments, you'll have to make sure that you update the fragment's views with the new data. But as for you adapter, you can do something like this:
public class MaPagerAdapter extends FragmentPagerAdapter {
private Meteo[]lameteo;
private FragmentManager fm;
public MaPagerAdapter(FragmentManager fm, Meteo[]lameteo) {
super(fm);
this.fm = fm;
this.lameteo = lameteo;
Log.i("aa", "ville maPagerAdapter"+lameteo[0].getLoc());
}
public void setData(Meteo[] meteo){
this.lameteo=meteo;
page_droite droite = (page_droite) getFragment(0);
droite.setData(meteo[0]);
page_gauche gauche = (page_gauche) getFragment(1);
gauche.setData(meteo);
}
private Fragment getFragment(int position) {
String tag = "android:switcher:" + R.id.viewpager + ":" + getItemId(position);
return fm.findFragmentByTag(tag);
}
Note that there is no need to call notifyDataSetChanged in this case, since you still have the same fragment in your adapter. When you implement the setData functions in your fragments, you need to make all the changes then.

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.

Categories

Resources