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.
Related
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;
}
}
Okay i'll try and make this as clear as possible. I have a Fragment called CheckerManager which contains a ViewPager. This ViewPager will allow the user to swipe between 3 Fragments all of which are an instance of another Fragment called CheckerFragment. I'm using a FragmentPagerAdapter to handle paging. Here's how it looks
private class PagerAdapter extends FragmentPagerAdapter {
CharSequence mTabTitles[];
public PagerAdapter(FragmentManager fm, CharSequence tabTitles[]) {
super(fm);
mTabTitles = tabTitles;
}
#Override
public Fragment getItem(int position) {
switch(position) {
case 0:
return CheckerFragment.newInstance(MainFragment.DRAW_TITLE_LOTTO);
case 1:
return CheckerFragment.newInstance(MainFragment.DRAW_TITLE_DAILY);
case 2:
return CheckerFragment.newInstance(MainFragment.DRAW_TITLE_EURO);
default:
return null;
}
}
#Override
public CharSequence getPageTitle(int position) {
return mTabTitles[position];
}
#Override
public int getCount() {
return 3;
}
}
I know that the ViewPager will always create the Fragment either side of the current Fragment. So say my 3 CheckerFragments are called A, B and C and the current Fragment is A. B has already been created. But my problem is that even though I am still looking at Fragment A, Fragment B is the 'active' Fragment. Every input I make is actually corresponding to Fragment B and not A. The active Fragment is always the one which has been created last by the ViewPager.
I've looked at quite a few things to see if anyone has had the same problem but i'm finding it difficult to even describe what's wrong. I think it's something to with the fact that all of the ViewPagers fragments are of the same type ie - CheckerFragment. I have a working implementation of a ViewPager inside a fragment elsewhere in the application and the only difference I can tell is that each page is a different type of Fragment.
Any help would be appreciated!
*EDIT
PagerAdapter adapter = new PagerAdapter(getChildFragmentManager(), tabTitles);
ViewPager viewPager = (ViewPager)view.findViewById(R.id.viewPagerChecker);
viewPager.setAdapter(adapter);
I feel pretty stupid but I found out what the issue was. In my CheckerFragment I would call getArguments() to retrieve a String extra and I would use this to determine how to layout the fragment. Problem was I made this extra a static member of CheckerFragment. So every time a new Fragment was created it was using the most recent extra.
Moral of the story - Don't make your fragments extra a static member if you plan on making multiple instances of that fragment.
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 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);
}
Using a ViewPager, I'm working on a guide that tells the user how to use my app.
This is how i currently add/setup the pages:
...
private class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
public ScreenSlidePagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return new Guide_fragment();
case 1:
return new Guide_fragment_2();
case 2,3,4 etc.
default:
return null;
}
}
...
But this way I have to have a fragment class for each page, and since the pages are only images and text, I figured that it might not be necessary.
Is there a way I can just use the same fragment class for all pages and then just assign a different layouts to it?
Thanks
Is there a way I can just use the same fragment class for all pages and then just assign a different layouts to it?
Sure. Pass data into the fragment indicating what to display, typically via the factory pattern:
Create a static newInstance() method that takes the data you might ordinarily pass to the fragment constructor
newInstance() takes those parameters, puts them in a Bundle, and attaches the Bundle to a newly-constructed instance of your fragment via setArguments()
Your fragment, when it needs this data, calls getArguments() to retrieve the Bundle
This ensures that your data will survive configuration changes.