I have an Activity called MainActivity which has a ViewPager and a TabLayout. The ViewPager has 4 (four) fragments that bound there. I have a problem about (maybe) FragmentStatePagerAdapter
I have my code run
public class MainPagerAdapter extends FragmentStatePagerAdapter {
Fragment[] fragments;
public MainPagerAdapter(FragmentManager fm) {
super(fm);
fragments = new Fragment[]{
new FragProfile(),
new FragScore(),
new FragMoney(),
new FragOther()
};
}
#Override
public Fragment getItem(int p) {
Log.w("Fragment", String.valueOf(p));
switch (p){
case 0:
return fragments[0];
case 1:
return fragments[1];
case 2:
return fragments[2];
case 3:
return fragments[3];
default:
return null;
}
}
#Override
public int getCount() {
return 4;
}
}
on first tab, i got this log came out
09-10 20:05:29.683 17034-17034/ampersanda.elsys W/Fragment: 0
09-10 20:05:29.684 17034-17034/ampersanda.elsys W/Fragment: 1
on second
09-10 20:11:03.970 17034-17034/ampersanda.elsys W/Fragment: 2
on third
09-10 20:11:54.534 17034-17034/ampersanda.elsys W/Fragment: 3
but on fourth i got nothing logged, and I back to the third i got this
09-10 20:14:07.373 17034-17034/ampersanda.elsys W/Fragment: 1
Back to second
09-10 20:14:50.241 17034-17034/ampersanda.elsys W/Fragment: 0
and I back to the first, i got nothing logged again
my code doesn't run well, but I got Fragment shows like its switch() but not about code inside it
In your case you should use FragmentPagerAdapter because when you use FragmentStatePagerAdapter the pages may get destroyed, so you will need to recreate them in getItem().
This version of the pager is more useful when there are a large number of pages, working more like a list view. When pages are not visible to the user, their entire fragment may be destroyed, only keeping the saved state of that fragment.
Related
I have a TabLayout with 4 tabs, what I want is to keep the first 3 tabs on ViewPager memory and recreate the fragment in the 4th tab everytime this tab is selected, is this possible?
This is my adapter:
public class SectionsPagerAdapter extends FragmentPagerAdapter {
private Fragment[] mFragments;
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
mFragments = new Fragment[4];
mFragments[0] = PlaceholderFragment.newInstance(0);
mFragments[1] = PlaceholderFragment.newInstance(1);
mFragments[2] = PlaceholderFragment.newInstance(2);
mFragments[3] = PlaceholderFragment.newInstance(3);
}
#Override
public Fragment getItem(int position) {
return mFragments[position];
}
#Override
public int getCount() {
// Show 4 total pages.
return 4;
}
#Override
public CharSequence getPageTitle(int position) {
switch (position) {
case 0:
return null;
case 1:
return null;
case 2:
return null;
case 3:
return null;
}
return null;
}
}
I know I am a little late to this.
You can use the offscreenPageLimit on the ViewPagerInstance to specify the amount of pages to keep alive on either side. The default value is 1 (Also the min value is 1. So you can't have every fragment recreated. IMHO this is probably done for smooth animations)
Check this documentation link for details offscreenPageLimit https://developer.android.com/reference/android/support/v4/view/ViewPager#setoffscreenpagelimit
As specified in the docs, be careful when tweaking this property. It can make the user experience better or worse based on how and where to use it.
Now for the second part of your question about recreating only a particular fragment. Check this documentation link https://developer.android.com/reference/android/support/v4/app/Fragment#setUserVisibleHint(boolean)
This basically should be called whenever your fragment is made visible to the user or not.
public class YourFragmentThatNeedsToBeRecreated extends Fragment {
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if (isVisibleToUser) {
// write your refresh logic here
}
}
Though you might face problems with this approach. It doesn't work perfectly for some reason (Insert a rant about fragments here). You can try attaching and detaching your fragment in the refresh logic. But I hope there's a cleaner solution for it.
I have a PagerAdapter which creates 3 fragments.
In the MainActivity I set the ViewPager like this:
ViewPager pager = (ViewPager) findViewById(R.id.pager);
pager.setOffscreenPageLimit(2);
pager.setAdapter(new PagerAdapter(getSupportFragmentManager()));
the pager.setOffScreenPageLimit(2) is from here https://stackoverflow.com/a/11852707/1662033, to make sure OnViewCreated is called once for each fragment.
Here is my PagerAdapter class:
public class PagerAdapter extends FragmentPagerAdapter {
public PagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public CharSequence getPageTitle(int position) {
switch (position) {
case 0:
return "Home";
case 1:
return "Live";
case 2:
return "Gallery";
default:
return null;
}
}
#Override
public int getCount() {
return 3;
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return new HomeFragment();
case 1:
return new LiveFragment();
case 2:
return new GalleryFragment();
default:
return null;
}
}
}
In the current code: all of the fragments's onCreateView, onActivityCreated etc are called once, at the beginning and that's it.
The issue I am having is - in one of the fragments (LiveFragment) I have a custom view which connects to a camera and shows the live stream.
What I want is - to inflate the view of LiveFragment only when the user navigates to the fragment, instead of how its now - its inflated at the beginning with the other fragments.
Is there a way to call onCreateView only when fragment is chosen?
FragmentPagerAdapter creates all the Fragments and has all of them in memory at all time. i.e. All your Fragments are created only once and you can navigate around them.
FragmentStatePagerAdapter creates and has only 3 Fragments (the current Fragment and the left and right Fragments to the current one) in memory at any given time, by default. You cannot reduce that number. However, you can increase the number Fragments in memory by using the viewpager.setOffScreenPageLimit().
Since you have only 3 Fragments, all your 3 fragments are created when the Viewpager is initialised. You can track which Fragment is currently visible on the screen using viewpager.addOnPageChangeListener(). Using this you can change the View of your LiveFragment from dummy one to actual View only when the Fragment is currently visible.
am having three fragments say A,B and C, one with viewpager that it contains multiple fragments(say fragment B has) in it.
while switching to fragment b will render the fragment viewpager contents after moving to other fragment and back to fragment b, here it will reload the content again.
i just want to stop destroying fragments once it get rendered??
Thanks in Advance.
sample code of adapter:
public class QuestionsSortPagerAdapter extends FragmentPagerAdapter {
int mNumOfTabs;
public QuestionsSortPagerAdapter(FragmentManager fm, int NumOfTabs) {
super(fm);
this.mNumOfTabs = NumOfTabs;
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
QuestionsSortByVotes byVotes = new QuestionsSortByVotes();
return byVotes;
case 1:
QuestionsSortByActivity byActivity = new QuestionsSortByActivity();
return byActivity;
case 2:
QuestionsSortByHot byHot = new QuestionsSortByHot();
return byHot;
case 3:
QuestionsSortByDate byDate = new QuestionsSortByDate();
return byDate;
case 4:
QuestionsSortByMonth byMonth = new QuestionsSortByMonth();
return byMonth;
default:
return null;
}
FragmentPagerAdapter will keep all fragments, it just destroy the view of fragment which is invisible to user, for example, if you scroll page from 1 to 2, then the view of page 0 will be destroyed, but the instance of page 0 will still be retained by adapter.
You can change this default behaviour by calling the method ViewPager.setOffscreenPageLimit(int limit), if you set limit to 2, then the view of page 0 will also be retained.
Set the number of pages that should be retained to either side of the current page in the view hierarchy in an idle state.
Okay i'll try and make this as clear as possible. I have a Fragment called CheckerManager which contains a ViewPager. This ViewPager will allow the user to swipe between 3 Fragments all of which are an instance of another Fragment called CheckerFragment. I'm using a FragmentPagerAdapter to handle paging. Here's how it looks
private class PagerAdapter extends FragmentPagerAdapter {
CharSequence mTabTitles[];
public PagerAdapter(FragmentManager fm, CharSequence tabTitles[]) {
super(fm);
mTabTitles = tabTitles;
}
#Override
public Fragment getItem(int position) {
switch(position) {
case 0:
return CheckerFragment.newInstance(MainFragment.DRAW_TITLE_LOTTO);
case 1:
return CheckerFragment.newInstance(MainFragment.DRAW_TITLE_DAILY);
case 2:
return CheckerFragment.newInstance(MainFragment.DRAW_TITLE_EURO);
default:
return null;
}
}
#Override
public CharSequence getPageTitle(int position) {
return mTabTitles[position];
}
#Override
public int getCount() {
return 3;
}
}
I know that the ViewPager will always create the Fragment either side of the current Fragment. So say my 3 CheckerFragments are called A, B and C and the current Fragment is A. B has already been created. But my problem is that even though I am still looking at Fragment A, Fragment B is the 'active' Fragment. Every input I make is actually corresponding to Fragment B and not A. The active Fragment is always the one which has been created last by the ViewPager.
I've looked at quite a few things to see if anyone has had the same problem but i'm finding it difficult to even describe what's wrong. I think it's something to with the fact that all of the ViewPagers fragments are of the same type ie - CheckerFragment. I have a working implementation of a ViewPager inside a fragment elsewhere in the application and the only difference I can tell is that each page is a different type of Fragment.
Any help would be appreciated!
*EDIT
PagerAdapter adapter = new PagerAdapter(getChildFragmentManager(), tabTitles);
ViewPager viewPager = (ViewPager)view.findViewById(R.id.viewPagerChecker);
viewPager.setAdapter(adapter);
I feel pretty stupid but I found out what the issue was. In my CheckerFragment I would call getArguments() to retrieve a String extra and I would use this to determine how to layout the fragment. Problem was I made this extra a static member of CheckerFragment. So every time a new Fragment was created it was using the most recent extra.
Moral of the story - Don't make your fragments extra a static member if you plan on making multiple instances of that fragment.
I'm testing my app with 'destroy all activities as soon as user leaves it' to simulate the OS killing my app.
In my main activity, in onCreate, I instantiate all the fragments, add them to a list and then:
mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager(), fragments);
mViewPager = (ViewPager) super.findViewById(R.id.pager);
mViewPager.setAdapter(mSectionsPagerAdapter);
Here is the adapter code:
public class SectionsPagerAdapter extends FragmentPagerAdapter {
private List<Fragment> fragments;
public SectionsPagerAdapter(FragmentManager fragmentManager, List<Fragment> fragments) {
super(fragmentManager);
this.fragments = fragments;
}
#Override
public Fragment getItem(int position) {
return fragments.get(position);
}
#Override
public int getCount() {
return fragments.size();
}
#Override
public CharSequence getPageTitle(int position) {
switch (position) {
case 0: return getString(R.string.section_one);
case 1: return getString(R.string.section_two);
case 2: return getString(R.string.section_three);
case 3: return getString(R.string.section_four);
}
return null;
}
}
Now, the problem (crash) lies here, in an action bar item:
case R.id.action_refresh:
PostsFragment fragment = (PostsFragment) mSectionsPagerAdapter.getItem(mViewPager.getCurrentItem());
fragment.reloadDataFromServer();
Say the activity was opened, then destroyed on the back button. When I return to this activity and press the refresh action item, the app crashes. Why? When the activity is created again, it creates a new pager adapter, with all new 4 fragments. But onCreate of these fragments is never called, none of the members are initialized, so the reloadDataFromServer fails loading into a listview that hasn't been initialized yet.
HOWEVER, the onCreate of the previous referenced fragments IS called, but we no longer have a reference to them because we recreated the FragmentPagerAdapter with new fragments. This is driving me INSANE! Why is onCreate not called for the new fragments?
In case someone has problems with this, I'll explain my solution. Basically what happens is fragments that were created before are restored on recreation of the activity after a destruction. This may be a duh moment for some of you.
My problem is essentially having incorrect references to fragments.
First, I changed the setOffscreenPageLimit to the full (#tabs - 1). This means all fragments are created at the start and held in memory. I'll explain why this helps later.
This means in onSaveInstanceState, we can use putFragment on all 4 fragments with the outState Bundle.
In create, if savedInstanceState is null, we can create all 4 fragments from scratch. Otherwise, you can use getFragment on all 4 fragments and place them into the new pager adapter to hold as reference.
You may ask why I set setOffscreenPageLimit to max. I had problems with figuring out which fragments to save with putFragment. isAdded() did not seem to always work properly, neither did isResumed(), so it was easier to just save all of them because we know all 4 were created and were in the fragment manager at all times.