I 'm using a viewpager as a questionnaire. The user inputs their answers in the first 8 fragments, then their answers are displayed in the 10th fragment (OverviewFragment). It's all working great. The issue I have a question about is when the user returns to a past fragment, ie: Fragment 4 and changes an answer, if they return to the 10th fragment it doesn't update. I'm sure this is a lifecycle/backstack issue but unsure how to kick fragments off the backstack in a viewpager. Or maybe just a way to ensure that fragment is constantly refreshed?
public class SectionsPagerAdapter extends FragmentStatePagerAdapter {
private int fragmentCount = 11;
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
switch (position){
case 0:
return DemoFragment.newInstance();
case 1:
return PreliminaryQuesFragment.newInstance();
case 2:
return QuestionOneFragment.newInstance();
case 3:
return QuestionTwoFragment.newInstance();
case 4:
return QuestionThreeFragment.newInstance();
case 5:
return QuestionFourFragment.newInstance();
case 6:
return QuestionFiveFragment.newInstance();
case 7:
return QuestionSixFragment.newInstance();
case 8:
return StatementFragment.newInstance();
case 9:
return OverviewFragment.newInstance(user.getTitle(), prelimAnswer, qOne, qTwo,
qThree, qFour, qFive);
case 10:
return GoodbyeFragment.newInstance(position);
default:
return null;
}
}
#Override
public int getCount() {
return fragmentCount;
}
}
It's likely an issue with how your Fragment's state is being restored when a Fragment that has been previously created is navigated back to -- is your FragmentStatePagerAdapter really creating a NEW fragment instance, or reusing an old one it's already been created, and using it's previous Bundle args?
I've run into a similar issue when creating Fragment's and passing a full Parcelable object as the UI Model to use when populating the Fragment's UI. Once the Fragment was navigated back to, the original Parcelable was used when recreating the Fragment's View (so, the old, original object from the Bundle args I set on the Fragment on it's initial creation). To get around this issue, I passed an ID reference to each Fragment in it's Bundle args instead (so, just some integer or something), that mapped to the present, in memory copy of the model Object I was trying to display in the UI.
So, even though my Fragment was recreated with the same bundle args, I would use SomeInMemoryHelperObjectContainingMyMap.get(ID) to get the most recent version of that object to populate the UI.
Related
Here is a piece of code from getItem method of FragmentPagerAdapter:
#Override
public Fragment getItem(int position) {
Fragment fragment = null;
switch (position){
case 0:
fragment = new FragmentLight();
break;
case 1:
fragment = new FragmentDark();
break;
}
return fragment;
}
QUESTIONS:
Why is fragment declared as "Fragment fragment = null;", and what is the meaning of null?
Why can I not just put here "Fragment fragment;" what is a difference between
these two examples?
In the first case you are giving an initial value to the object, and that's null value. In the second case you would be leaving the object uninitialized.
The important part is the code that follows: if you can guarantee that in any of the branches the code may take, the object will get initialized, than you can leave it uninitialized at the beginning. Otherwise, if there is no guarantee, the compiler will complain (assuming that you will be using that object later on, otherwise you'll get a warning for unused variable).
I'm using a ViewPager and displaying a lot of different Fragments inside it, not only in content but they use different classes as well. The list to be displayed should be changed dynamically and even though I manage to swap items around and add new ones to the adapter(and calling notifyDataSetChanged), if I try changing the next item it will still slide to it when using mPager.setCurrentItem(mPager.getCurrentItem() + 1);
I am just adding a new Fragment between the current item and the current next one, it is displayed correctly in the adapter but as the next one was already preloaded then getItem in the adapter is not even called.
Is there another method "stronger" than notifyDataSetChanged that tells my ViewPager that it should get the next item again?
CODE SAMPLES:
The add and get item methods inside my FragmentPagerAdapter(only samples, not the actual code)
public void add(#NonNull Integer fragmentIndex) {
mFragmentOrder.add(fragmentIndex);
notifyDataSetChanged();
}
#Override
public Fragment getItem(int position) {
int selectedFragment = mFragmentOrder(position);
Fragment fragment;
switch (selectedFragment) {
case 1:
fragment = new FragmentA();
break;
case 2:
fragment = new FragmentB();
break;
case 3:
fragment = new FragmentC();
break;
default:
fragment = new FragmentD();
break;
}
return fragment;
}
This is the function used to go to the next item(I don't allow swiping)
public void goToNext() {
mPager.setCurrentItem(mPager.getCurrentItem() + 1);
}
EDITS:
Edit 1: I had already tried using a FragmentStatePagerAdapter instead and setting the OffscreenPageLimit to 0, but to no avail.
Edit 2: [Solution] Using a FragmentStatePagerAdapter AND overwriting the getItemPosition function to return POSITION_NONE or the index in the appropriate cases solved the problem. For some reason even after implementing the right version of this function the normal FragmentPagerAdapter kept delivering the wrong Fragment.
By default, FragmentPagerAdapter assumes that the number and positions of its items remain fixed. Therefore, if you want to introduce for dynamism, you have to provide for it yourself by implementing the getItemPosition(Object object) method in the inherited adapter class. A very basic (but unefficient) implementation would be this:
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
Every time the parent view is determining whether the position of one of its child views (items) has changed, this code will force the fragment to be recreated. If you want to avoid the recreation when unnecessary, you have to include some logic in the method. Something like this:
#Override
public int getItemPosition (Object object) {
if (fragmentOrder.indexOf(object) == -1) {
return POSITION_NONE;
} else {
return index;
}
}
Finally, pay attention to possible memory leaks by adding an onDestroyView method to your fragments and nullifying the views you are using.
Here is a good discussion of these issues with the two PagerAdapters.
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.
I am trying to wrap my head around the proper use of fragments with the ViewPager. I currently have 5 fragments (with one main activity) each of which pulls some data from a web service and displays it to the user. The user can swipe between the views to see the different info. The problem I am running into is the fragments get destroyed and recreated once the user swipes two pages away and then back to the fragment resulting in multiple calls to the web services when they are not needed. Configuration changes also force the fragments to be recreated (thus recalling the webservice). I am currently recreating a new instance of the fragment every time. Whats the best way to cache the data? Or am I using FragmentPagerAdapter in the wrong way? I've attached some relevant code.
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
WebFragmentOne a = WebFragmentOne.newInstance();
return a;
case 1:
WebFragmentTwo b = WebFragmentTwo.newInstance();
return b;
case 2:
WebFragmentThree c = WebFragmentThree.newInstance();
return c;
case 3:
WebFragmentFour d = WebFragmentFour.newInstance();
return d;
case 4:
WebFragmentFive f = WebFragmentFive.newInstance();
return f;
}
return null;
}
The newInstance() method in each of the fragments.
public static final WebFragment newInstance()
{
WebFragment f = new WebFragment();
return f;
}
Keep the data you want to keep in a private field within the fragment.
In the OnCreate call setRetainInstance(true) to stop Android destroying your fragment.
Now you can check when the fragment starts if you already have data. If you don't then you can retrieve it from your web service. If you do then skip the retrieval and jump straight to the setting up of your views.
So I'm trying to create a swipe tab view in my app. But the problem I'm having is that my fragments need to be FragmentActivity becuase they have listview inside them.
So I think this is where I'm getting my error:
public Fragment getItem(int index) {
switch (index) {
case 0:
// Top Rated fragment activity
return new TopRatedFragment();
case 1:
// Games fragment activity
return new GamesFragment();
case 2:
// Movies fragment activity
return new MoviesFragment();
case 3:
//Other fragment activity
return new OtherFragment();
}
return null;
}
This needs to be:
public FragmentActivity getItem(int index) {
switch (index) {
case 0:
// Top Rated fragment activity
return new TopRatedFragment();
case 1:
// Games fragment activity
return new GamesFragment();
case 2:
// Movies fragment activity
return new MoviesFragment();
case 3:
//Other fragment activity
return new OtherFragment();
}
return null;
}
But I keep getting errors on this line:
public FragmentActivity getItem(int index) {
Error:
The return type is incompatible with FragmentPagerAdapter.getItem(int)
Does anyone know what I'm doing wrong here?
My class is extending FragmentPagerAdapter
A Fragment can also have a ListView in it.
FragmentActivity is some support Class, to support Fragments on older devices, i think its pre api 11.
To use a ListView in a Fragment, just create a ListView in the Fragment, or inflate an xml file with a ListView in it.
FragmentPagerAdapter getItem() method needs to return a Fragment. FragmentActivity is not a Fragment, it's an Activity containing Fragments. See here: http://developer.android.com/reference/android/support/v4/app/FragmentPagerAdapter.html
Use ListFragment for Fragments having a ListView
http://developer.android.com/reference/android/support/v4/app/ListFragment.html
Your fragment don't need to be FragmentActivity to hold a ListView, actually the Activity should extend FragmentActivity, and your fragment should extend ListFragment, which is:
a fragment that displays a list of items by binding to a data source
such as an array or Cursor, and exposes event handlers when the user
selects an item.
I made a post about it that explains how fragments work, how to implement a ListView inside a fragment and how to integrate Fragments with ViewPager. Here is the link, hope it helps.