Fragment re-created when change tab with ViewPager - android

I've got a ViewPager with 4 tabs.
I'm using a custom adapter that extends FragmentPagerAdapter.
The problem is that when I change the tab, the first Fragment gets created again although it's not the currently visible.
Moreover, this only happens after changing the tab for the fourth time. E.g. tab1 -> tab2 -> tab3=> onStop from tab1 is called. -> tab2 => onCreateView from tab1 is called (onAttach is not called).
I want to avoid this. I need all tabs to remain always in their state even when they are not currently displayed.
How can I do this?

It's been as easy as using method setOffscreenPageLimit() from ViewPager.
More info: http://developer.android.com/reference/android/support/v4/view/ViewPager.html#setOffscreenPageLimit(int)

Viewpager has one public method named setOffScreenPageLimit which indicates the number of pages that will be retained to either side of the current page in the view hierarchy in an idle state.
Activity:
private SectionsPagerAdapter mSectionsPagerAdapter;
private ViewPager mViewPager;
#Override
protected void onCreate(Bundle savedInstanceState) {
...
mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
mViewPager = findViewById(R.id.container);
mViewPager.setOffscreenPageLimit(3); // change your number here
mViewPager.setAdapter(mSectionsPagerAdapter);

When you instantiate the fragment, call setRetainInstance(true). This will retain the instance state of the fragment. onCreateView will still be called and you will need to make sure that you re use the saved state and just render the views without loading any data again, e.g. by using a boolean to represent if your fragment has already been initialized.
You would do something like this:
public static class MyAdapter extends FragmentPagerAdapter {
#Override
public Fragment getItem(int position) {
MyFragment myFragment = new MyFragment();
myFragment.setRetainInstance(true);
return myFragment;
}
}
class MyFragment extends Fragment {
.
.
.
boolean initialized = false;
ArrayList<MyData> myData = null;
void onCreate(...) {
if (initialized) {
renderViews();
} else {
initialized = true;
loadData();
renderViews();
}
}
}
You don't need to implement onSaveInstanceState(). It will be handled automatically.

Related

Fragment onStart() and onStop() not called when switching to another tab in tabbed layout

public class HomeActivity extends AppCompatActivity {
Context context = HomeActivity.this;
private SectionsPagerAdapter mSectionsPagerAdapter;
private ViewPager mViewPager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
mSectionsPagerAdapter = new
SectionsPagerAdapter(getSupportFragmentManager());
mViewPager = findViewById(R.id.container);
mViewPager.setAdapter(mSectionsPagerAdapter);
TabLayout tabLayout = findViewById(R.id.tabs);
mViewPager.addOnPageChangeListener(new
TabLayout.TabLayoutOnPageChangeListener(tabLayout));
tabLayout.addOnTabSelectedListener(new
TabLayout.ViewPagerOnTabSelectedListener(mViewPager));
}
public class SectionsPagerAdapter extends FragmentPagerAdapter {
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
switch (position){
case 0 : return new HomeFrag1();
case 1 : return new HomeFrag2();
case 2 : return new HomeFrag3();
}
return null;
}
#Override
public int getCount() {
return 3;
}
}
}
When i am switching from fragment 1 to fragment 2 the onStop method of fragment 1 is not called but it gets called when i switch from fragment 2 to fragment 3. When i switch from fragment 2 to fragment 1 the onStart is not called either.
There's a property on ViewPager called offscreenPageLimit - that is set by default to 1, meaning that ViewPager will persist 1 page from either side of the current page - that's why the aforementioned lifecycle callbacks are not being invoked. If you really want to keep only one fragment in-memory you can setOffscreenPageLimit to 0, and that way you will get the behavior you're looking for.
According to this article:
When the page is no longer visible or adjacent to the visible page the ViewPager asks the adapter to destroy it.
The above seems to explain your problem. Switching from 1->2 won't try to destroy Page 1 since it's adjacent to Page 2 which is visible. But switching 2->3 will destroy Page 1 as it's no longer adjacent to the now visible Page 3.
Similarly, switching 2->1 won't re-create Page 1 as it hasn't been destroyed previously.
I suggest you try overriding onPause() and onResume() and see if that works as you'd like.

Fragment how to enter onCreateView lifecycle method

I want to create a new instance of a Fragment in a PagerAdapter with some method like Fragment1.newInstance(0);
but this does not enter the Fragment onCreateView method, where I want to take its layout... How can I make it enter the lifecycle method?
EDIT: For the swipe tabs I am using an external library, that extends PagerAdapter, maybe thats the problem?
Everything you need is described here. See section "Adding a fragment to an activity" especially to solve your problem.
This is because ViewPager doesn't recreate fragment on each swipe .. it creates fragment only once and keeps in memory and recreate once after it goes offScreenpageLimit() ...For recreating fragment each time you swipe ,you have to set
mViewPager.setOffscreenPageLimit(0);
But this method is not recommended if you have so many pages in viewpager (atleast more than 10)..
in your pager adapter class return the instance like this
#Override
public Fragment getItem(int position){
return Fragment1.newInstance(position);
}
And in your Fragment create a static instance like this
public static NearbyOffersFragment newInstance(int position) {
Fragment1 f = new Fragment1();
Bundle b = new Bundle();
b.putInt("position", position);
f.setArguments(b);
return f;
}
and onCreate of your fragment retrieve the postion
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
position = getArguments().getInt("position");
}

Fragment incorrectly recreated while rotation

I have a FragmentActivity which shows some fragments by ViewPager. Here's my code snippet,
MyFragmentActivity
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity);
viewPager = (ViewPager) findViewById(R.id.pager);
fetchDataAsync(this);
}
#Override
private void onDataReceived(List<String> data) {
viewPager.setAdapter(new MyFragmentPageAdapter(getChildFragmentManager()), data);
}
MyFragmentPagerAdapter
private class MyFragmentPagerAdapter extends FragmentPagerAdapter {
private Data data;
public MyFragmentPagerAdapter(FragmentManager fm, Data data)
{
super(fm);
this.data = data;
}
#Override
public Fragment getItem(int position) {
return MyFragment.newInstance(data.get(position));
}
#Override
public int getCount() {
return data.size();
}
#Override
public CharSequence getPageTitle(int position) {
return null;
}
}
The fragments are created dynamically by FragmentPagerAdapter and depend on data received.
My app crashed when screen rotation happened. With some search, I learned that "When a config change occurs the old Fragment isn't destroyed - it adds itself back to the activity when it's recreated". At that moment the data was null for recreated fragment so it's crashed at onCreateView.
Someone said FragmentStatePagerAdapter should work but not for my case. Another said reusing the fragment instead of recreating new ones, but ViewPager creates them automatically and how can I manage this. I want to permanently destroy the old fragments and not re-add them to activity, what could I do?
UPDATED
I removed constructor param from MyFragment, and passed it by setArgument
Bundle b = new Bundle();
b.putString("data", data.toString());
f.setArguments(b);
Now fragment can restore data while recreating since MyFragment could save state(data) in this way when config changes happened
You should handle the orientation change yourself.
It is covered by developer resources under configuration change, basically, this means adding to your AndroidManifest the attribute configChanges="orientation". Then when orientation changes, Activity.onConfigurationChanged will be called.
See for example Activity under Configuration Changes
Normally, this manual handle will result in your fragment being laid out, and so its size should change automatically.
Another solution, is to keep it like this : i mean, on orientation change, your
Activity is being recreated, and so is your Fragment.
So you need to ensure in your fragment that the data are available, eventually loading them if not.

ViewPager Fragments missing after popBackStack

I am having problems with fragments or maybe rather the way I want them to work.
In my activity I have a layout container (R.id.container1) to which I add a fragment (FragMain) programmatically at startup.
In the fragment I have 2 Fragments (Frag1, Frag2) and a ViewPager which loads several other fragments (FragA, FragB, FragC, FragD, FragE) via a FragmentPagerAdapter.
On a button press I replace the whole content of R.id.container1, so FragMain is replaced with another Fragment (FragSub).
Now when I press the back button, FragMain is loaded again, but the ViewPager isn't fully initialized, some Fragments are missing. From what I observed, it's always FragB and FragD missing, If I scroll to the empty views, the app crashes. The other fragments seem to be fine. What's going wrong here?
This thread suggests using getChildFragmentManager in creation of the PagerAdapter, but I am already doing that... Fragment in ViewPager not restored after popBackStack
I am accessing the fragments via e.g.
((FragA)((PagerAdapter)viewPager.getAdapter()).getItem(0));
Is this the best way?
Some Code :
MainActivity's onCreate
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
pagerFragList = new Vector<Fragment>();
pagerFragList.add(Fragment.instantiate(this, FragA.class.getName()));
pagerFragList.add(Fragment.instantiate(this, FragB.class.getName()));
pagerFragList.add(Fragment.instantiate(this, FragC.class.getName()));
pagerFragList.add(Fragment.instantiate(this, FragD.class.getName()));
pagerFragList.add(Fragment.instantiate(this, FragE.class.getName()));
FragMain fragMain = (FragMain)getSupportFragmentManager().findFragmentByTag(FragMain.debugTag);
if(fragMain==null) fragMain = new FragMain();
FragmentHelper.replaceSupportFragment(this, fragMain, R.id.container1, false, FragMain);
}
Helper
public Vector<Fragment> getFragList() {
return pagerFragList;
}
FragMain's onViewCreated
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
pagerAdapter = new PagerAdapter(getChildFragmentManager(), ((MainActivity)getActivity()).getFragList());
viewPager = ((ViewPager)view.findViewById(R.id.viewPager));
viewPager.setAdapter(pagerAdapter);
viewPager.setOffscreenPageLimit(5);
viewPager.setCurrentItem(0, true);
}
The PagerAdapter
public class PagerAdapter extends FragmentPagerAdapter {
private List<Fragment> fragments;
public PagerAdapter(FragmentManager fm, List<Fragment> fragments) {
super(fm);
this.fragments=fragments;
}
#Override
public Fragment getItem(int position) {
return fragments.get(position);
}
#Override
public int getCount() {
return fragments.size();
}
}
Have no idea why this seems to be fixing it on my end, but try commenting out your "viewPager.setOffscreenPageLimit(5);" line.
I'm still investigating and will be back with more if I find anything else useful out.
The other thing it turns out that I had to do was override the "state" methods of a FragmentStatePagerAdapter like this:
#Override
public Parcelable saveState() {
return null;
}
#Override
public void restoreState(Parcelable state, ClassLoader loader) {
//do nothing
}
In order to bypass whatever strange thing that was causing problems (must have had something to do with saving and restoring the states of nested fragments in a ViewPager...
There are alot of permutations involving the state of fragments and backstack transitions. For debug, it may help to isolate the event you are working with (backbutton , orientation change ... ) and to look at the following before and after your event....
state of fragment manager which includes the backstack i believe...
//debug
getFragmentManager().dump("", null,
new PrintWriter(System.out, true), null);
see this thread

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");
}

Categories

Resources