Dynamically changing fragment contents in a ViewPager - android

I have:
A ViewPager with a FragmentPagerAdapter on (or a FragmentStatePagerAdapter, doesn't really solve my problem).
A fixed number of fragments. They all share the same layout, but have TextViews that need to be set differently;
An AsyncTask that queries my database and retrieves content to be set into the TextViews.
So my code was:
public class StatsPagerAdapter extends FragmentPagerAdapter {
static final int FRAGMENT_COUNT = 5;
private Parameters[] sectionData;
public StatsPagerAdapter(FragmentManager manager) {
super(manager);
this.sectionData = new Parameters[FRAGMENT_COUNT];
}
#Override
public Fragment getItem(int position) {
return StatsSectionFragment.getInstance(this.sectionData[position]);
}
#Override
public int getCount() {
return FRAGMENT_COUNT;
}
public void setSectionData(int position, Parameters sectionData) {
this.sectionData[position] = sectionData;
notifyDataSetChanged();
}
}
So I'm passing sectionData[position] to the getInstance() method of my generic sub-fragment. That data should differentiate each instance of the fragments loaded into the ViewPager.
At first I'll be passing an empty reference, but then in my async class:
#Override
protected void onProgressUpdate(Parameters... sectionValues) {
super.onProgressUpdate(sectionValues);
mPagerAdapter.setSectionData(sectionId, sectionValues[0]);
}
That should call the setSectionData() above, update sectionData[position] and generate a call to notifiyDataSetChanged(). I was hoping that doing so would make the adapter retrieve all its items again, thus calling getItem() again, and loading new fragments.
Sadly it does not. So right now:
if fragments (i.e., getItem()) are created before my async task result are published, that item will stay empty (i.e.,visible fragment, but with empty text views since I never called getInstance(non-null stuff).
That happens for fragment 0 && fragment 1.
if fragments are created after my async task has ended (fragment 2 to end, because getItem() is called only when you reach that fragment by swiping), then the first and only call to getItem() produces a getInstance(non-null stuff), and that's ok.
How can I reload content into those already-there fragments, or force the adapter to call getItem() and update its views?

Add this to your adapter:
#Override
public int getItemPosition(Object object){
return PagerAdapter.POSITION_NONE;
}
This make your adapter call getItem again when you call notifyDataSetChanged();

Related

Prevent specific fragment within viewpager from refreshing

I have several fragments within a viewpager, and the first fragment (TimelineFragment) is being replaced whenever the user chooses to browse a different community.
I'm able to successfully change the TimelineFragment when I changed my FragmentPagerAdapter to FragmentStatePagerAdapter since FragmentPagerAdapter does not update the fragment even when I use pagerAdapter.notifyDataSetChanged()
Hence, I used FragmentStatePagerAdapter with the following code:
#Override
public Fragment getItem(int position) {
return fragmentList.get(position);
}
public void setItem(int position, Fragment fragment) {
fragmentList.set(position, fragment);
}
#Override
public int getCount() {
return fragmentList.size();
}
#Override
public int getItemPosition(Object object) {
return PagerAdapter.POSITION_NONE;
}
The problem is whenever I move to a fragment, that fragment is being refreshed. Which makes my recyclerview within each fragment reload all lists and go to the top of that list.
My goal here is that when the user has chosen to view a different community, the timeline fragment (ALONE, hopefully exluding other fragments) will be the only fragment to reload, and load the respective list based on the chosen community.
the method onCreateView in fragment calls each time you move between 3 fragments in view pager. so the solution is:
1- add static boolean loaded=false; in your fragment
2- before calling the code that refreshes the recyclerview or any code you don't want to call add
if(!loaded){
//your code
...
loaded=true;
}

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
}

dynamically modifying list of Fragments while using ViewPagerAdapter

I have a ViewPager and ViewPagerAdapter as defined below:
private static class ViewPagerAdapter extends FragmentPagerAdapter {
#Override
public int getCount() {
return fragments.size();
}
#Override
public Fragment getItem(int position) {
return fragments.get(position);
}
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
}
Within my activity I am populating fragments array which is my list of Fragments which I can view by scrolling horizontally:
// populate fragments
fragments.add(....);
pager_adapter = new ViewPagerAdapter(getSupportFragmentManager());
view_pager.setAdapter(pager_adapter);
All this works fine. But, now I have a situation where in at run time I have to add one more fragment to this list and then refresh the view so that the new fragments becomes visible too upon scrolling. For this I added the following code inside my activity:
public static void addPageAndRefresh(Page page) {
fragments.add(PinFragment.newInstance(fragments.size(), true, page, book, false, false, null));
pager_adapter.notifyDataSetChanged();
}
After this, I do get a new fragment, but it is not the one which I added, its a copy of the last fragment in the last before calling notifyDataSetChanged().
Is this the correct way of doing this? It looks like although the fragments array is updated properly, but the view is not picking up the updated array. But, if it isn't then I shouldn't be seeing the new fragment after scrolling.
I tried similar requirement as yours with a FragmentStatePagerAdapter which worked fine. May be you would want to try it.
To add the new fragment:
SIZE = SIZE +1;
adapter.instantiateItem(viewPager, SIZE-1);
adapter.notifyDataSetChanged();
In getItem(), for the "position" create and return the new fragment (I used a switch statement here)
Update the getCount() method to return the new SIZE.
Let me know if you would like me to share the code.

How do you notify a PagerAdapter that the fragments have changed when the count is the same?

I have built an application with a navbar on the left, whose main content is a ViewPager
The ViewPager slides between two different views.
When the user selects something from the navgation bar, I send a message to the ViewPager's adapter (I have tried both FragmentPagerAdapter and FragmentStatePagerAdapter for this, both won't work) which sets an internal variable and calls notifyDatasetChanged();
The problem is that the getCount() method always returns 2 , so when the adapter checks to see if the dataset has changed, it sees that the items are still 2 and does not go on to call getItem(position).
The getItem(position) returns different fragments according to the value of the internal variable that is set before notifyDatasetChanged();
I tried overriding getItemId(position) in case the pager checks for the id, but it seems to not bother after checking the count.
Is there a way to force the adapter to rebuild the fragments when notifyDatasetChanged() is called?
Thanks in advance for any help you can provide
Edit: here is the code I am currently using:
public class ContentAdapter extends FragmentPagerAdapter {
private ViewedSection _section = ViewedSection.Main;
public ContentAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
ViewedScreen screen = get_screen(position);
return screen == null ? null : FragmentFactory.GetFragment(get_screen(position));
}
#Override
public int getCount() {
return 2;
}
private ViewedScreen get_screen(int position) {
//code to resolve which screen will be shown according to the current position and _section
}
public void set_page(ViewedSection section) {
this._section = section;
notifyDataSetChanged();
}
}
So when the user clicks on a NavBar item, I call ((ContentAdapter)_pager.getAdapter()).set_page(section);
For this, you need to override
public int getItemPosition (Object object)
Return POSITION_UNCHANGED if you don't want to replace the fragment. Return POSITION_NONE if you want to replace the fragment. (Also, you can return a new position to move the fragment to.)
A common override is
public int getItemPosition (Object object) {
return POSITION_NONE;
}
which will just rebuild everything.

Replace a fragment within a Viewpager(notifyDataSetChanged does not work)

My MainActivity contains a viewPager.
In the MainActivity.java, I set the adapter for viewpager. The adapter extends FragmentStatePagerAdapter. The fragment I want to replace is a
cameraFragment. So when the user clicks on the switch camera button, I want to now show the camera fragment, this time with a front camera on.
On clicking the switch Camera button, I remove the fragment from the arraylist of fragments I had passed to the custom adapter. I add the new fragment and call notifydatasetchanged. However, this does not result in the new fragment being added. How do I achieve dynamic replacement of fragments within a viewpager which is backed my a custom fragment state pager adapter?
Code :
mainPageFragments = new ArrayList<>();
mainPageFragments.add(new ResultsFragment_());
mainPageFragments.add(DemoCameraFragment_.newInstance(false));
pagerAdapter = new MainViewPagerAdapter(getSupportFragmentManager(),mainPageFragments);
To replace the fragment : On receiving the related event I do,
mainPageFragments.remove(1);
if (event.getCameraState().equals(CameraSwitchButton.CameraTypeEnum.BACK)) {
mainPageFragments.add(DemoCameraFragment.newInstance(false));
} else {
mainPageFragments.add(DemoCameraFragment.newInstance(true));
}
// Not Working...
pagerAdapter.notifyDataSetChanged();
Adapter Code :
public class MainViewPagerAdapter extends FragmentStatePagerAdapter {
ArrayList<Fragment> fragmentsArray;
public MainViewPagerAdapter(FragmentManager fm, ArrayList<Fragment> fragmentsArray) {
super(fm);
this.fragmentsArray = fragmentsArray;
}
#Override
public Fragment getItem(int position) {
return fragmentsArray.get(position);
}
#Override
public int getCount() {
return fragmentsArray.size();
}
#Override
public int getItemPosition(Object object) {
return super.getItemPosition(object);
}
}
Your MainViewPagerAdapter.getItemPosition is the cause of your issue.
Default implementation always returns POSITION_UNCHANGED. For pager to remove your fragment you have to return PagerAdapter.POSITION_NONE for the fragments that are removed.
Additionally your current design contradicts with the idea of FragmentStatePagerAdapter. From the FragmentStatePagerAdapter documentation: "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. This allows the pager to hold on to much less memory associated with each visited page as compared to FragmentPagerAdapter at the cost of potentially more overhead when switching between pages."
Your current implementation holds all fragments in an array, and so defeats this mechanism. Correct implementation would be to create fragments in MainViewPagerAdapter.getItem method and let the adapter to handle fragments lifecycles as needed.
Thanks to #Okas. I made the following change to getItemPosition within my FragmentStatePagerAdapter subclass.
#Override
public int getItemPosition(Object object) {
if (object instanceof DemoCameraFragment_)
return POSITION_NONE;
return super.getItemPosition(object);
}
I added logs to the OnCreate of both my fragments to confirm if they were getting recreated or not. As per my requirement, only the second fragment is recreated.

Categories

Resources