Hi there I am wondering what is the correct way if I would like to add different instance of a Fragment which uses the same Layout with different content in a ViewPager.
So for a better understanding I create my Fragments with an createInstance() method and pass an id with which it gets content for a list from a database. I add them to my FragmentPagerAdapter.
So what I get is the first Fragment added multiple times without getting its content for the actual id.
How could I force the ViewPager to treat the Fragments individually ?
*Edit:
Okay I totally failed within my createInstance() method which was more a getInstance() method and returned the same instance every time...
You need to set adapter.notifyDataSetChanged.
Refer below
class MyFragmentAdapter extends FragmentPagerAdapter{
public MyFragmentAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
return new PagerFragment(arrayListUri.get(position),position);
}
#Override
public int getCount() {
return 0;
}
}
Related
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.
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();
I am using the standard Android tabs and I need to access my tabs from the parent activity.
I do the following in my MainActivity to get my tabs:
myTab = ((FirstTabFragment)mAdapter.getItem(index));
The problem is that I always get a new object and not the instance because the getItem is implemented as follows:
public class TabsPagerAdapter extends FragmentPagerAdapter {
public TabsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int index) {
switch (index) {
case 0:
// Status fragment activity
return new FirstTabFragment();
case 1:
// Mission fragment activity
return new SecondTabFragment();
case 2:
// Team fragment activity
return new ThirdTabFragment();
}
return null;
}
...
I googled quite a lot but I still can't find any working solution to get the instance ob my Fragments.
I need the instance because I need to alter the fragment's views and therefore I need it's variables.
Any ideas?
Thanks!
The best way is to use the FragmentManager with the method findFragmentById or findFragmentByTag. Of course, you need to declare an ID or a TAG for each fragment created by the FragmentPagerAdapter. If I remember correctly, the default implementation uses the class name as tag.
Save the fragment in SparseArray or in HashMap. Add the fragment in array in your getItem method also override the onFragmentDestroy method. In method remove the item from SparseArray or HashMap. Now create a getter method somthing like this
public Fragment getFragment(int pos) {
return array.get(pos);
}
I am trying to make use of the viewpager and the new tab design in android (new being 11+). I see that in the examples, setArgument/getArguments is used to pass info to the Fragment.
I want to pass an object (good size object). I was wondering, why not call a setter instead to avoid serializiation and all that?
For example
public class SectionsPagerAdapter extends FragmentPagerAdapter {
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
Fragment fragment = new DummySectionFragment();
fragment.setMyCustomInfo(myObject); // method I created to set info
return fragment;
}
instead of:
public class SectionsPagerAdapter extends FragmentPagerAdapter {
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
Fragment fragment = new DummySectionFragment();
fagment.setMyCustomInfo(myObject);
args.putSerializable("Serial", myObject);
fragment.setArguments(args);
return fragment;
}
why not call a setter instead to avoid serializiation and all that?
As I said in my comment, with a setter you'll need to manually call it to pass the data when the Fragment is restored(this is kind of the case of a constructor with arguments of a Fragment which will not get called in certain situation when the system automatically restores the Fragment(this will call the no arguments constructor)). By using setArguments() you get this for free and you avoid potential problems.
Another way to approach this is to make the Fragment retrieve the data directly from the Activity(which will not call any setter). Because you're using the fragments in a ViewPager calling a setter will require that you first find the fragments and this could be error prone. If the Fragment retrieves the data on its own you'll be updating only the right fragments at the right time.
Large objects in the Fragment arguments will sooner or later fail because there is a certain maximum size that you can have in an intent (I think it's around 1 mB).
Everything that's hard to parcel/serialize or is very large (things holding images for example) should be set via setter. You will have to take care to set the data at the appropriate time. Fragment pagers are especially tricky with this.
Is it possible to use one fragment in a viewpager multiple times? I am trying to build a dynamically updated UI using ViewPager.
I want to use the same design, basically the same fragment with different data for every page, like a listview adapter.
You can instantiate the same Fragment class for every page in your ViewPager, passing the position of the ViewPager to control what to display. Something like that:
public class MyFragment extends Fragment {
private int mIndex;
public MyFragment(int index) {
mIndex = index;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
switch(mIndex){
case 0:
// do you things..
case 1:
// etcetera
}
}
}
then, in you FragmentPagerAdapter:
public static class MyAdapter extends FragmentPagerAdapter {
public MyAdapter(FragmentManager fm) {
super(fm);
}
#Override
public int getCount() {
return NUM_ITEMS;
}
#Override
public Fragment getItem(int position) {
return new MyFragment(position);
}
}
That way you can reuse most of your code changing only what you need in the switch/case statement.
You misunderstood the concept of class versus object of class. Your MyFragment.java source code defines class which you turn into "living thing" each time you instantiate it with new operator (new MyFragment();) - this creates object which is an instance of your class. Unless you intentionally prevent this (by i.e. using Singleton pattern) your can make as many instances of the class as you like, same way you are allowed to make as many i.e. cakes using single recipe. And this applies to fragments as well.
So as long as you create separate object (aka said instance) of your class for each page, you should be able to do what you want.