For Multiple Tabs Single Fragment not working getItem called twice - android

Sir, I have to use single fragment for multiple tabs. I have implemented it using FragmentStatePagerAdapter but after debugging I have got that some of the tabs displays data but not correct. This is because getItem called twice please help me as early as possible.
I have passed arguments from getItem. it passes arguments two times as getItem calls twice. thus it overrides data of arguments. I have 6 tabs but only 1, 3 and 5 tab displays data but data is of 2, 4 and 6th tab and 2, 4 and 6th tab not showing anything on the screen.
not working any solution from previous questions.
ViewPager Code
public class ViewPagerAdapter extends FragmentStatePagerAdapter {
int TAB_COUNT=5;
Context mContext;
public ViewPagerAdapter(FragmentManager fm,Context context) {
super(fm);
mContext=context;
}
#Override
public Fragment getItem(int position) {
NewsFragment fragment = new NewsFragment();
Bundle args = new Bundle();
args.putString("title", String.valueOf(getPageTitle(position)));
args.putString("data", "data");
fragment.setArguments(args);
return fragment;
}
#Override
public CharSequence getPageTitle(int position) {
return mContext.getResources().getStringArray(R.array.tabs)[position];
}
#Override
public int getCount() {
return TAB_COUNT;
}
}
Listeners to change tab :
#Override
public void onTabSelected(TabLayout.Tab tab) {
viewPager.setCurrentItem(tab.getPosition());
}
#Override
public void onPageSelected(int position) {
TabLayout.Tab tab = tabLayout.getTabAt(position);
if (tab != null) {
tab.select();
}
}
Adapter setting
private void setUpTab() {
viewPagerAdapter = new ViewPagerAdapter(getSupportFragmentManager(), this);
for (int i = 0; i < viewPagerAdapter.getCount(); i++) {
tabLayout.addTab(
tabLayout.newTab()
.setText(viewPagerAdapter.getPageTitle(i)));
}
tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
viewPager.setAdapter(viewPagerAdapter);
viewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(tabLayout));
viewPager.addOnPageChangeListener(this);
tabLayout.addOnTabSelectedListener(this);
}

getItem gets called every time the adapter creates a new page for the first time. The reason you see it being called twice is that the viewPager prepares the next page in advance, so if you enter page 1, page 2 is already being created too for faster paging when you swipe right.
To help you further please show us the code for NewsFragment.
Also, you might consider using FragmentPagerAdapter instead of FragmentStatePagerAdapter because you seem to have a fixed number of tabs (5 in your example) and according to the documentation that is the use case for FragmentPagerAdapter.
Documentation from the page I linked above:
This version of the pager is best for use when there are a handful of typically more static fragments to be paged through, such as a set of tabs.

Related

ViewPager holds fragment's instance even after setting a new adapter

I'm facing some issues with ViewPager and Fragment's instances.
I have a ViewPager (let's call Father) with 4 fragments and into the last fragment I have another ViewPager (and call it Child) with dynamic fragments amount. What I mean is that I create the Child based on a list of objects in memory. So if the list contains 3 objets, the Child will have 3 fragments inside. If in a determinate moment something happens and I get a list with 1 object, then I must update the Child with just 1 fragment. An important point in here is that each Child'sFragment has its own object from the returned list and is created based on this object.
The code I do to set the list of fragments into the Child ViewPager is the following:
#Override
public void setViewPagerChildFragments(List<Fragment> fragments) {
if (fragments != null) {
DefaultStateViewPagerAdapter adapter = (DefaultStateViewPagerAdapter) mViewpagerChild.getAdapter();
if (adapter == null) {
/* In this case I use getChildFragmentManager() because
it's inside the last fragment of the ViewPager Father*/
adapter = new DefaultStateViewPagerAdapter(getChildFragmentManager(), fragments);
mViewpagerChild.setAdapter(adapter);
} else {
adapter.setFragments(fragments); // Into this method I do notifyDataSetChanged() already
}
}
}
Note that I try to use the same adapter's instance to set the fragments and then notify the changes (notifyDataSetChanged() is inside the method). If I don't get the adapter's instance, I create a new one and set it to the ViewPager Child.
The problem happens, for example, when I set the ViewPager Child with 2 fragments and after a while I need to set it with 1 fragment. The ViewPager shows just 1 fragment inside it, but the second one is still attached and isn't destroyed. I know it because I did a test calling getChildFragmentManager().getFragments(), and I could see the fragment which was supposed to be destroyed is still there.
You may say it isn't actually a problem, since the Garbage Collector can remove the unused Fragment. However, if in some moment for example, I try to set 2 new fragments again into the ViewPager Child, it uses that unused Fragment instance instead of creating a new one and unfortunately it also uses its same object, and not the right new one.
This is my DefaultStateViewPagerAdaptercode:
public class DefaultStateViewPagerAdapter extends FragmentStatePagerAdapter {
private ArrayList<Fragment> mFragments;
private ArrayList<String> mFragmentTitles;
public DefaultStateViewPagerAdapter(FragmentManager fm) {
super(fm);
this.mFragments = new ArrayList<>();
this.mFragmentTitles = new ArrayList<>();
}
public DefaultStateViewPagerAdapter(FragmentManager fm, List<Fragment> fragments) {
super(fm);
this.mFragments = (ArrayList<Fragment>) fragments;
}
#Override
public Fragment getItem(int position) {
return mFragments.get(position);
}
#Override
public int getItemPosition(Object object) {
int index = mFragments.indexOf(object);
if (index < 0) {
index = POSITION_NONE;
}
return index;
}
#Override
public int getCount() {
return mFragments.size();
}
#Override
public CharSequence getPageTitle(int position) {
return mFragmentTitles.get(position);
}
public void clearFragments() {
mFragments.clear();
mFragmentTitles.clear();
}
public void setFragments(List<Fragment> fragments) {
mFragments = (ArrayList<Fragment>) fragments;
notifyDataSetChanged();
}
public void addFragment(Fragment fragment, String title) {
mFragments.add(fragment);
mFragmentTitles.add(title);
}
}
I already tried to override saveState in order to avoid the ViewPager uses the old fragment´s instance, like:
#Override
public Parcelable saveState() {
return null;
}
It worked, and the ViewPager no longer uses the old reference, but the unused Fragment is still attached, and it causes memory leak.
I don't know why the ViewPager doesn't destroy its fragments even after I set a new adapter. Has anyone ever had this issue?
I found the solution.
The solution is very simple. I just had to set manually null to the Child's adapter. With this, the ViewPager is forced to destroy every fragment.
So into the onDestroyView of Fragment's father I added:
#Override
public void onDestroyView() {
super.onDestroyView();
mViewpagerChild.removeOnPageChangeListener(mOnPaymentMethodsPageChangeListener);
mViewpagerChild.setAdapter(null); // <-- This is what I added
}

TabLayout with ViewPager not working as expected

There is 3 TabLayout with ViewPager in my application.
If I swipe for change Tabs then it behaves strangely.
Below is the scenario.
Let's say 3 Tabs A, B, C.
Default Tab A will open when I start App.
Go to B From A - Works OK.
Go to A From B - Works Ok.
Go To B From A - Works Ok.
Go To C From B - Works Ok.
Now Problem Starts here.
Go To B From C - It will Load A and B Both but display Only B.
Why A and B loads if I come from C ??
Below is my code.
private Toolbar toolbar;
private TabLayout tabLayout;
private ViewPager viewPager;
ViewPagerAdapter adapter;
toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
viewPager = (ViewPager) findViewById(R.id.viewpager);
setupViewPager(viewPager);
tabLayout = (TabLayout) findViewById(R.id.tabs);
tabLayout.setupWithViewPager(viewPager);
private void setupViewPager(ViewPager viewPager) {
adapter = new ViewPagerAdapter(getSupportFragmentManager());
adapter.addFragment(new FragmentOpen(), "OPEN");
adapter.addFragment(new FragmentClose(), "CLOSE");
adapter.addFragment(new FragmentTest(), "TEST");
viewPager.setAdapter(adapter);
}
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);
}
}
I want to load Tabs every time.
For that, I use below code.
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
if(adapter != null){
adapter.getItem(position).onResume();
}
Toast.makeText(getApplicationContext(), "Page Selected", Toast.LENGTH_LONG).show();
}
#Override
public void onPageScrollStateChanged(int state) {
}
});
But it will always load both tabs when I came from C.
What I want is: only Tab B must load when came from C, not Both.
Its not possible when using ViewPager. ViewPager loads at least one right/left adjacent fragment of current fragment as per your swipe direction.
If you read the ViewPager documentation, there is a method ViewPager.setOffscreenPageLimit(LIMIT) which supports off screen page limit and its default value is 1.
SetOffscreenPageLimit:
Set the number of pages that should be retained to either side of the
current page in the view hierarchy in an idle state. Pages beyond this
limit will be recreated from the adapter when needed.
This is offered as an optimization. If you know in advance the number
of pages you will need to support or have lazy-loading mechanisms in
place on your pages, tweaking this setting can have benefits in
perceived smoothness of paging animations and interaction. If you have
a small number of pages (3-4) that you can keep active all at once,
less time will be spent in layout for newly created view subtrees as
the user pages back and forth.
You should keep this limit low, especially if your pages have complex
layouts. This setting defaults to 1.
See documentation.
Here are some related answers:
1. ViewPager.setOffscreenPageLimit(0) doesn't work as expected
2. How can make my ViewPager load only one page at a time ie setOffscreenPageLimit(0);
3. How does setOffscreenPageLimit() improve ViewPager performance by retaining more offscreen Fragments?
its Default behavior of ViewPager if you are in A then it loads B
also and when your reach B it will Load C and A also is Loaded now
when to reach at C its destroy the View of A and now when you come
again to B it will Load A again, in a simple way ViewPager Maintain
the One Next and One previous state of View by Default.
to Overcome this you should use ViewPager.setOffscreenPageLimit(number) here number is the count of page to load Simulationally for Next and Previous View.
I had only 3 tabs and settingViewPager.setOffscreenPageLimit(2) worked for me.
Try ViewPager.setOffscreenPageLimit(totTabs - 1)

How to delete a Tab and its associated fragment from TabLayout & FragmentStatePagerAdapter

I am trying to create a program with TabLayout and an option menu which when selected can add or delete the tab along with the fragment. I can delete the tab but I am not able to understand what else needs to be done because I have to delete the fragments that the tab holds, more over I may also have to rearrange the position of the fragments which I just can't seem to figure out. Could you please help me with this ?
under mainAcitivity this is how I add tabs
tabLayout = (TabLayout) findViewById(R.id.tab_layout);
tab1= tabLayout.newTab().setText("TAB 1");
tabLayout.addTab(tab1);
tab2 = tabLayout.newTab().setText("TAB 2");
tabLayout.addTab(tabKantipur);
This is the FragmentstatePagerAdapter
public class TabPagerAdapter extends FragmentStatePagerAdapter {
int tabCount;
public TabPagerAdapter(FragmentManager fm, int numberOfTabs) {
super(fm);
this.tabCount = numberOfTabs;
}
#Override
public int getItemPosition(Object object) {
return super.getItemPosition(object);
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return NewsFragment.newInstance("data for fragment 1");
case 1:
return NewsFragment.newInstance("data for fragment 2");
default:
return null;
}
}
#Override
public int getCount() {
return tabCount;
}
}
Basically here NewsFragment is the fragment whose new instance is used in the tabs. Under onActivityResult I am getting the data that has been requested from the options Menu to add or delete fragmetns
mpagerAdapter = new TabPagerAdapter(getSupportFragmentManager(), tabLayout.getTabCount());
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
removedTab = ..data received from ooption menu
// here I want to remove the Tab & the fragments (tab1 & its fragment)
if (removeTab == "tab1") {
tabLayout.removeTab(tab1);
mpagerAdapter.notifyDataSetChanged();
}
}
As I do this only the first tab gets removed I would like to know how to remove the associated fragment too.
You don't have to explicitly delete the fragment, you can remove the fragment.
You can remove the tab and do notifyDataSetChanged on the viewPagerAdapter instance.It will take care of removing the fragment.
I had asked a similar question using fragmentarray for which i got it to work . this is the link, Issue: Logic to find the position of a fragment in a fragment arraylist?

Updating fragments/views in viewpager with fragmentStatePagerAdapter

Need some help with my problem of updating pages while using viewpager. I am using a simple viewpager with FragmentStatePagerAdapter. All I want to do is get access to the current fragment/view so as I can update some textviews in my fragment/view. I searched around in the forum and came to know few things
- One of the ways to handle this is by setting tag in instantiateItem() call back of the adapter and retreive the view by findViewbyTag. I could not understand how to implement this and I am also not sure if that will work for FragmentStatePagerAdapter.
- I explored other options as suggested in various forums, but cannot make them work.
My code is very much same as in a android http://developer.android.com/training/animation/screen-slide.html. Basic components are the same (Fragment activity xml with some display components including a textview, viewpager xml with just a view pager in it, a Fragment class and the main FragmentActivity class). in my FragmentActivity class I have added a pageChangelistener to my viewpager so as I can do my textview changes during onPageSelected().
Any help is is appreciated.
Adding the code for reference.
Public class myActivity extends FragmentActivity
//Variable declarations
protected void onCreate(Bundle savedInstanceState) {
ViewPager mPager = (ViewPager) findViewById(R.id.pager);
mPagerAdapter = new ScreenSlidePagerAdapter(getSupportFragmentManager());
mPager.setAdapter(mPagerAdapter);
View CurrView;
OnPageChangeListener pageChangelistener = new OnPageChangeListener() {
#Override
public void onPageSelected(int pageSelected) {
doTextViewChnges();//access the text view and update it based on pageSelected
---THIS IS WHERE I AM STUCK IN TRYING TO GET THE TEXTVIEW IN MY CURRENT FRAGMWNT/VIEW-------
}
mPager.setOnPageChangeListener(pageChangelistener);
}
private class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
public ScreenSlidePagerAdapter(android.support.v4.app.FragmentManager fm) {
super(fm);
}
#Override
public android.support.v4.app.Fragment getItem(int position) {
return ScreenSlidePageFragment.create(position, <other parameters I want to pass>);
}
#Override
public int getCount() {
return NUM_PAGES;
}
}
in your OnPageChangeListener:
OnPageChangeListener pageChangelistener = new OnPageChangeListener() {
#Override
public void onPageSelected(int pageSelected) {
ScreenSlidePageFragment currentFragment = (ScreenSlidePageFragment) mPagerAdapter.getItem(pageSelected)
doTextViewChnges();//access the text view and update it based on pageSelected
---THIS IS WHERE I AM STUCK IN TRYING TO GET THE TEXTVIEW IN MY CURRENT FRAGMWNT/VIEW-------
}
i assume you want to access your fragments from your activity and update the views.Fragments are parts of activities and their views can be accessed by them. Fragments are created dynamically in viewadapters and there is no straight forward and easy way to Tag them. You may simply access a fragment by its index number.first one is 0 second one is 1 and so on...
//access your fragment(in your case your first fragment)
//this part should be inside your doTextViewChnges()
Fragment fragment = (Fragment ) adapterViewPager
.getFragment(0);
if (fragment != null) {
//call a public method inside your fragment to update your text view
fragment .updateYourTextView();
}
Edit: inside your fragment create the following method and update your textview from there.
void updateYourTextView() {
yourTextView.setText("yourtext");
}

One Fragment with multi inner fragments for many tabs

I want a tab layout in which, on every tab there are two fragments, One above showing a progress of a task that is completed by giving input in 3 tabs, from start to end, and another main fragment below that progress fragment that will be taking input.
That makes a title and detail in every tab, title fragment same and detail fragment (input fragment different on each tab)!
I tried all methods for two days :(
My Solution
i tried a layout that contains two framelayout for main fragment that is added into two tabs from ViewPager,showed here:
public static class ScreenSlidePagerAdapter extends
FragmentStatePagerAdapter {
public static ArrayList<Fragment> tabs_fragments = new ArrayList<Fragment>(
3);
public ScreenSlidePagerAdapter(FragmentManager fm,
ArrayList<Fragment> tabs) {
super(fm);
ScreenSlidePagerAdapter.tabs_fragments = tabs;
}
#Override
public Fragment getItem(int arg0) {
return tabs_fragments.get(arg0);
}
#Override
public int getCount() {
return tabs_fragments.size();
}
#Override
public int getItemPosition(Object object) {
if (tabs_fragments.contains(object)) {
return POSITION_UNCHANGED;
}
return POSITION_NONE;
}
public static void setItem(Fragment fr, int position) {
if (position <= 2 && position >= 0) {
tabs_fragments.remove(position);
tabs_fragments.add(position, fr);
} else
Log.d("adding tab", "wrong tab position to add fragment at");
}
}
On activity start i do this, see i get tab position that is used in main fragment for loading detail fragment for different tab positions:
mPager = (ViewPager) findViewById(R.id.pager);
// fragments for pagers
ArrayList<Fragment> tabs = new ArrayList<Fragment>();
tabs.add(new FrEventDetails());
tabs.add(new FrEventDetails());
tabs.add(new FrRuleSave());
mPagerAdapter = new ScreenSlidePagerAdapter(getFragmentManager(), tabs);
mPager.setAdapter(mPagerAdapter);
bar = getActionBar();
bar.setTitle("Add Rule");
bar.setSubtitle("select event for rule");
bar.setDisplayShowTitleEnabled(true);
bar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
bar.setDisplayOptions(0, ActionBar.DISPLAY_SHOW_TITLE);
bar.setDisplayHomeAsUpEnabled(true);
bar.addTab(bar.newTab().setText("Select Event")
.setTabListener(new TabListener(mPager)));
bar.addTab(bar.newTab().setText("Select Action")
.setTabListener(new TabListener(mPager)));
bar.addTab(bar.newTab().setText("Save Rule")
.setTabListener(new TabListener(mPager)));
if (savedInstanceState != null) {
bar.setSelectedNavigationItem(savedInstanceState.getInt("tab", 0));
}
mPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
#Override
public void onPageSelected(int position) {
// When swiping between pages, select the
// corresponding tab.
if (bar.getSelectedNavigationIndex() != position)
bar.setSelectedNavigationItem(position);
tab = position;
}
and then when this fragment is attaching to activity I add a progress layout (title( for one frame-layout and based on the selected tab, add another fragment for detail frame layout(for input).
showed here:
public class FrEventDetails extends Fragment {
Context context;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
context = (Context) activity;
int tab = AddRule1.tab;
android.app.FragmentTransaction ft = activity.getFragmentManager()
.beginTransaction();
ft.add(R.id.fr_rule_progress_container, new FrProgress());
if (tab == 1) {
ft.replace(R.id.fr_event_input_container, new FrActionSelect(),
"cell1_1").commit();
} else if (tab == 0) {
ft.replace(R.id.fr_event_action_container, new FrEventSelect(),
"cell1_2").commit();
}
}
NOTE* layout for this main fragment is simple containing two framelayouts.
result : It shows on start a title and detail beautifully on first tab, but on second tab, only main fragment and,
when i swipe to third fragment and come back, second fragment is filled beautifully (after 1 second of showing) but 1st fragment is now empty.
i can't understand what to do with this.
please give some solution to this tearful problem of two days
To Show all Tabs at the same time in start, increase PagerOffSetLimit (to 2 in this case for 3 tabs)
For refreshing state of fragment or calling some methods on your fragment in the tab beside your current tab, use the Pager.instantiateItem method.
One second delay is because you have on the animateLayoutChanges in your xml.

Categories

Resources