This question already has answers here:
ViewPager and fragments — what's the right way to store fragment's state?
(11 answers)
Closed 7 years ago.
I'have 1 FragmentActivity with a ViewPager which handle 2 Fragments.
public class MyFragmentActivity extends FragmentActivity{
private Fragment f1;
private Fragment f2;
private ViewPager myPager;
private MyFragmentAdapter mFragmentsAdapter;
private static ArrayAdapter<Fragment> mFragmentArray;
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.my_layout);
myViewPager = (ViewPager) findViewById(R.id.pager_acciones);
mFragmentArray = new ArrayAdapter<Fragment>(getApplicationContext(),android.R.layout.simple_list_item_1);
f1 = new Fragment();
f1 = new Fragment();
mFragmentArray.add(f1);
mFragmentArray.add(f2);
mFragmentsAdapter = new MyFragmentAdapter(getSupportFragmentManager());
myPager.setAdapter(mFragmentsAdapter);
myPartidoPager.setCurrentItem(0);
}
public static class MyFragmentAdapter extends FragmentPagerAdapter {
public AccionesFragmentAdapter(FragmentManager fm) {
super(fm);
}
#Override
public int getCount() {
return mFragmentArray.getCount();
}
#Override
public Fragment getItem(int position) {
return mFragmentArray.getItem(position);
}
}
My problem is that every time the screen orientation changes, the activity is created and also the Fragments. I don't mind if the activity is created again, but I don't want the Fragments to be recreated.
The easiest way is to throw a if statement right after the on create method is called and check to see if you already have something in there. If savedInstanceState is not null, you don't need to do anything:
if (savedInstanceState == null){
// do whatever
} else {
// dont do anything
}
(this answer is more for people that stumble upon this question cause they can't figure out why fragments get re-added every time they turn their device)
You could add:
android:configChanges="screenSize|orientation"
In AndroidManifest.xml.
This will prevent Android calling onCreate on screen orientation change. If you want to perform special handling of orientation change, you can override onConfigurationChanges.
Save your FragmentAdapter in onSaveInstanceState and return it to the Activity after a configuration change with onRetainNonConfigurationInstance so the fragments survive an orientation change.
Related
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.
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
As viewpager preload +1 and -1 of the fragment that is used in it. I've a loading of data using asyntask that i wish to execute only when i am on the page itself.
Where should i execute the function? oncreate does not seems to work for me
You can implement ViewPager.OnPageChangeListener and run your AsyncTask in onPageSelected().
For example:
public class MyActivity implements ViewPager.OnPageChangeListener {
#Override
public void onPageSelected(int position) {
new MyAsyncTask().execute();
}
}
However, as Tyczj pointed out in the comments, this defeats the purpose of a ViewPager trying to keep Views loaded. This feature is designed to make your app look smooth, and without it your Views will look empty (or take on their default appearance) while you load your data.
The solution provided by Tanis should work, there's however one think that should be taken in consideration. Since the AsyncTask is started from Activity, you may encounter some issues when dealing with configuration changes. Perhaps starting the AsyncTask dirrectly from the fragment will make more sense.
The solution then would be to make the currently displayed fragment aware that he is the fragment displayed now.
Firstly, you should have a method in your Activity that will return the position of current fragment from ViewPager:
public class MainActivity extends Activity{
//....
public int getViewPagerCurrentIndex() {
return pager.getCurrentItem();
}
}
Secondly, in your PagerAdapter in getItem() method pass the position of current item as an argument to the fragment:
public class MyPagerAdapter extends FragmentStatePagerAdapter {
//....
#Override
public Fragment getItem(int position) {
return MyFragment.newInstance(position);
}
}
Lastly, check in MyFragment that both position returned from MainActivity and position received when the fragment was instantiated match. If they match then this instance of fragment is visible:
public static MyFragment newInstance(int position) {
MyFragment fragment = new MyFragment ();
Bundle args = new Bundle();
args.putInt(KEY_POSITION, position);
fragment.setArguments(args);
return fragment;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
if (getArguments() != null) {
fragmentPosition = getArguments().getInt(KEY_POSITION);
}
}
// It does not matter where this method is called, the AsyncTask will be started only for the currently visible fragment.
private void executeAsyncTask(){
MainActivity mainActivity = (MainActivity)getActivity();
if (mainActivity.getViewPagerCurrentIndex() == fragmentPosition ) {
new MyAsyncTask().execute();
}
}
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.
I have following issue. I have ViewPager and FragmentPagerAdapter for it.
All I need to do is call some method from my Fragment which is in ViewPager when this page was selected.
I tried to keep all my Fragments in List inside my adapter, but the problem is that when I rotate my device Adapter use previous fragments. New fragments were created after rotation and I have list of them, but I don't now how to get access to previous fragments. I have references to new fragments only.
Here is my adapter:
public class MainPagerAdapter extends FragmentPagerAdapter {
private List<Fragment> fragmentList;
public MainPagerAdapter(FragmentManager fm) {
super(fm);
fragmentList = new ArrayList<Fragment>();
fragmentList.add(new TaskPageFragment());
fragmentList.add(new HistoryFragment());
fragmentList.add(new TestFragment());
}
#Override
public Fragment getItem(int i) {
return fragmentList.get(i);
}
#Override
public int getCount() {
return 3;
}
}
Of course when I trying to access to fragment which was only created like an object, I can't get access to Activity Context.
Fragment fragment = pagerAdapter.getItem(i);
if (fragment instanceof SelectionListener) {
((SelectionListener)fragment).onTabSelected();
}
Here is onTabSelected() method call is shown. It's my interface which I implemented to each fragment in ViewPager and when it gets called after screen rotation I get null Context
Device configurations can change during runtime (such as screen orientation). When this happens the Activity is restarted. This is done so your activities can adjust to match the new device configuration, and since fragments are within Activities they are also 'destroyed'.
However your issue may be solved with something simple, since from what I understand from your question you are not looking for UI configuration changes and assuming you are using API 11+.
public void setRetainInstance (boolean retain)
Since: API Level 11
Control whether a fragment instance is retained across Activity re-creation (such as from a configuration change). This can only be used with fragments not in the back stack. If set, the fragment lifecycle will be slightly different when an activity is recreated:
onDestroy() will not be called (but onDetach() still will be, because
the fragment is being detached from its current activity).
onCreate(Bundle) will not be called since the fragment is not being
re-created.
onAttach(Activity) and onActivityCreated(Bundle) will
still be called.
public MainPagerAdapter(FragmentManager fm) {
Fragment frag = null;
super(fm);
fragmentList = new ArrayList<Fragment>();
frag = new TaskPageFragment();
frag.setRetainInstance(true);
fragmentList.add(frag);
frag = new HistoryFragment();
frag.setRetainInstance(true);
fragmentList.add(frag);
frag = new TestFragment();
frag.setRetainInstance(true);
fragmentList.add(frag);
}