Tabs with ViewPager - How to get instance and not new object? - android

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);
}

Related

How to reuse layouts/fragments in ViewPager

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;
}
}

I've got a weird bug when using a ViewPager inside a Fragment

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.

Use setArguments or method for fragment in TABs

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.

Viewpager pages with different layouts but same fragment class

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.

reusing fragments in a fragmentpageradapter

I have a viewpager that pages through fragments. My FragmentPagerAdapter subclass creates a new fragment in the getItem method which seems wasteful. Is there a FragmentPagerAdapter equivalent to the convertView in the listAdapter that will enable me to reuse fragments that have already been created? My code is below.
public class ProfilePagerAdapter extends FragmentPagerAdapter {
ArrayList<Profile> mProfiles = new ArrayList<Profile>();
public ProfilePagerAdapter(FragmentManager fm) {
super(fm);
}
/**
* Adding a new profile object created a new page in the pager this adapter is set to.
* #param profile
*/
public void addProfile(Profile profile){
mProfiles.add(profile);
}
#Override
public int getCount() {
return mProfiles.size();
}
#Override
public Fragment getItem(int position) {
return new ProfileFragment(mProfiles.get(position));
}
}
The FragmentPagerAdapter already caches the Fragments for you. Each fragment is assigned a tag, and then the FragmentPagerAdapter tries to call findFragmentByTag. It only calls getItem if the result from findFragmentByTag is null. So you shouldn't have to cache the fragments yourself.
Appendix for Geoff's post:
You can get reference to your Fragment in FragmentPagerAdapter using findFragmentByTag(). The name of the tag is generated this way:
private static String makeFragmentName(int viewId, int index)
{
return "android:switcher:" + viewId + ":" + index;
}
where viewId is id of ViewPager
Look at this link: http://code.google.com/p/openintents/source/browse/trunk/compatibility/AndroidSupportV2/src/android/support/v2/app/FragmentPagerAdapter.java#104
Seems a lot of the people viewing this question are looking for a way to reference the Fragments created by FragmentPagerAdapter/FragmentStatePagerAdapter. I would like to offer my solution to this without relying on the internally created tags that the other answers on here use.
As a bonus this method should also work with FragmentStatePagerAdapter. See notes below for more detail.
Problem with current solutions: relying on internal code
A lot of the solutions I've seen on this and similar questions rely on getting a reference to the existing Fragment by calling FragmentManager.findFragmentByTag() and mimicking the internally created tag: "android:switcher:" + viewId + ":" + id. The problem with this is that you're relying on internal source code, which as we all know is not guaranteed to remain the same forever. The Android engineers at Google could easily decide to change the tag structure which would break your code leaving you unable to find a reference to the existing Fragments.
Alternate solution without relying on internal tag
Here's a simple example of how to get a reference to the Fragments returned by FragmentPagerAdapter that doesn't rely on the internal tags set on the Fragments. The key is to override instantiateItem() and save references in there instead of in getItem().
public class SomeActivity extends Activity {
private FragmentA m1stFragment;
private FragmentB m2ndFragment;
// other code in your Activity...
private class CustomPagerAdapter extends FragmentPagerAdapter {
// other code in your custom FragmentPagerAdapter...
public CustomPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
// Do NOT try to save references to the Fragments in getItem(),
// because getItem() is not always called. If the Fragment
// was already created then it will be retrieved from the FragmentManger
// and not here (i.e. getItem() won't be called again).
switch (position) {
case 0:
return new FragmentA();
case 1:
return new FragmentB();
default:
// This should never happen. Always account for each position above
return null;
}
}
// Here we can finally safely save a reference to the created
// Fragment, no matter where it came from (either getItem() or
// FragmentManger). Simply save the returned Fragment from
// super.instantiateItem() into an appropriate reference depending
// on the ViewPager position.
#Override
public Object instantiateItem(ViewGroup container, int position) {
Fragment createdFragment = (Fragment) super.instantiateItem(container, position);
// save the appropriate reference depending on position
switch (position) {
case 0:
m1stFragment = (FragmentA) createdFragment;
break;
case 1:
m2ndFragment = (FragmentB) createdFragment;
break;
}
return createdFragment;
}
}
public void someMethod() {
// do work on the referenced Fragments, but first check if they
// even exist yet, otherwise you'll get an NPE.
if (m1stFragment != null) {
// m1stFragment.doWork();
}
if (m2ndFragment != null) {
// m2ndFragment.doSomeWorkToo();
}
}
}
or if you prefer to work with tags instead of class member variables/references to the Fragments you can also grab the tags set by FragmentPagerAdapter in the same manner:
NOTE: this doesn't apply to FragmentStatePagerAdapter since it doesn't set tags when creating its Fragments.
#Override
public Object instantiateItem(ViewGroup container, int position) {
Fragment createdFragment = (Fragment) super.instantiateItem(container, position);
// get the tags set by FragmentPagerAdapter
switch (position) {
case 0:
String firstTag = createdFragment.getTag();
break;
case 1:
String secondTag = createdFragment.getTag();
break;
}
// ... save the tags somewhere so you can reference them later
return createdFragment;
}
Note that this method does NOT rely on mimicking the internal tag set by FragmentPagerAdapter and instead uses proper APIs for retrieving them. This way even if the tag changes in future versions of the SupportLibrary you'll still be safe.
Don't forget that depending on the design of your Activity, the Fragments you're trying to work on may or may not exist yet, so you have to account for that by doing null checks before using your references.
Also, if instead you're working with FragmentStatePagerAdapter, then you don't want to keep hard references to your Fragments because you might have many of them and hard references would unnecessarily keep them in memory. Instead save the Fragment references in WeakReference variables instead of standard ones. Like this:
WeakReference<Fragment> m1stFragment = new WeakReference<Fragment>(createdFragment);
// ...and access them like so
Fragment firstFragment = m1stFragment.get();
if (firstFragment != null) {
// reference hasn't been cleared yet; do work...
}
If the fragment still in memory you can find it with this function.
public Fragment findFragmentByPosition(int position) {
FragmentPagerAdapter fragmentPagerAdapter = getFragmentPagerAdapter();
return getSupportFragmentManager().findFragmentByTag(
"android:switcher:" + getViewPager().getId() + ":"
+ fragmentPagerAdapter.getItemId(position));
}
Sample code for v4 support api.
For future readers!
If you are thinking of reusing fragments with viewpager, best solution is to use ViewPager 2, since View Pager 2 make use of RecyclerView.
Medium article - Exploring the View Pager 2
Docs
Samples repo
Release notes
I know this is (theoretically) not an answer to the question, but a different approach.
I had an issue where I needed to refresh the visible fragments. Whatever I tried, failed and failed miserably...
After trying so many different things, I have finally finish this using BroadCastReceiver. Simply send a broadcast when you need to do something with the visible fragments and capture it in the fragment.
If you need some kind of a response as well, you can also send it via broadcast.
cheers

Categories

Resources