Related
When I start the app everything works ok but when I rotate to landscape it crashes because in the Fragment there is a field that is NULL.
I dont use setRetainInstance(true) or adding Fragments to FragmentManagerI create new Fragments on app start and when app rotate.
In the Activity OnCreate() I create the Fragment and adding them to the viewPager like this.
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ParentBasicInfoFragment parentBasicInfoFragment = new ParentBasicInfoFragment();
ParentUTCFragment parentUTCFragment = new ParentUTCFragment();
ParentEventsFragment parentEventsFragment = new ParentEventsFragment();
this.mFragments = new ArrayList<>();
this.mFragments.add(parentBasicInfoFragment);
this.mFragments.add(parentUTCFragment);
this.mFragments.add(parentEventsFragment);
this.viewpage.setOffscreenPageLimit(3);
setCurrentTab(0);
this.viewpage.setAdapter(new MainActivityPagerAdapter(getSupportFragmentManager(), this.mFragments));
}
Then I have a test button on the app that when I press it will do like
public void test(View view) {
((BaseFragment) MainActivity.this.mFragments.get(MainActivity.this.viewpage.
getCurrentItem())).activityNotifiDataChange("hello");
}
This will work and the current Fragments in the ViewPager have the method, activityNotifiDataChange() that are being called and all is ok.
When I rotate the app and do the same thing pressing the button the activityNotifiDataChange() is being called alright but there a null pointer exception because the ArrayList<Fragment> mFragment is now NULL.
HereĀ“s a small sample Android Studio project showing this behavior:
https://drive.google.com/file/d/1Swqu59HZNYFT5hMTqv3eNiT9NmakhNEb/view?usp=sharing
Start app and press button named "PRESS TEST", then rotate device and press the button again and watch the app crash
UPDATE SOLUTION thanks #GregMoens and #EpicPandaForce
public class MainActivityPagerAdapter extends PersistenPagerAdapter<BaseFragment> {
private static int NUM_ITEMS = 3;
public MainActivityPagerAdapter(FragmentManager fm) {
super(fm);
}
public int getCount() {
return NUM_ITEMS;
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return ParentBasicInfoFragment.newInstance(0, "Page # 1");
case 1:
return ParentUTCFragment.newInstance(1, "Page # 2");
case 2:
return ParentEventsFragment.newInstance(2, "Page # 3");
default:
return null;
}
}
}
public abstract class PersistenPagerAdapter<T extends BaseFragment> extends FragmentPagerAdapter {
private SparseArray<T> registeredFragments = new SparseArray<T>();
public PersistenPagerAdapter(FragmentManager fragmentManager) {
super(fragmentManager);
}
#Override
public T instantiateItem(ViewGroup container, int position) {
T fragment = (T)super.instantiateItem(container, position);
registeredFragments.put(position, fragment);
return fragment;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
registeredFragments.remove(position);
super.destroyItem(container, position, object);
}
public T getRegisteredFragment(ViewGroup container, int position) {
T existingInstance = registeredFragments.get(position);
if (existingInstance != null) {
return existingInstance;
} else {
return instantiateItem(container, position);
}
}
}
The main problem I see with your app is your misunderstanding with how FragmentPagerAdapter works. I see this a lot and it's due to lack of good javadocs on the class. The adapter should be implemented so that getItem(position) returns a new fragment instance when called. And then getItem(position) will only be called by the pager when it needs a new instance for that position. You should not pre-create the fragments and pass then into the adapter. You should also not be holding strong references to the fragments from either your activity or from parent fragments (like ParentBasicInfoFragment). Because remember, the fragment manager is managing fragments and you are also managing fragments by newing them and keeping references to them. This is causing a conflict and after rotation, you are trying to invoke activityNotifiDataChange() on a fragment that is not actually initialized (onCreate() was not called). Using the debugger and tracking object IDs will confirm this.
If you change your code so that the FragmentPagerAdapter creates the fragments when they are needed and don't store references to fragments or lists of fragments, you will see much better results.
this.mFragments = new ArrayList<>();
Because this is wrong. You should never hold a reference to the fragment list if you are using ViewPager. Just return new instance of Fragment from getItem(int position) of FragmentPagerAdapter and you are good to go.
But to fix your code, you must delete mFragments entirely.
See https://stackoverflow.com/a/58605339/2413303 for more details.
Use setRetainInstance(true) is not a good approach. If you need to same some simple information such as: position of recyclerView, selected item of recyclerView, maybe some model(Parcelable) you could do it with method onSaveInstanceState / onRestoreInstanceState, there is one limitation is 1MB. Here is an example with animations how it works.
For more durable persistance use SharedPreferences, Room(Google ORM) or you could try to use ViewModel with LiveData (best approach some data which should live while user session).
//change in AndroidManifest.xml file,
<activity
android:name=".activity.YourActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:screenOrientation="sensor"
/>
//YourActivity in which you defines fragment.
//may be it helps
I have a class which serves as my model.
class Model {
String name;
String address;
}
I have an array of objects of this class with 'n' elements.
List<Model> modelElements;
I am trying to implement a ViewPager with 'n' number of Fragments. Each fragment in the ViewPager is exactly the same but the data shown is picked from each element of the Model. I have implemented a basic Fragment which has two TextViews, each for showing the name and address.
I want to know how can I pass the object of Model class to each Fragment. So modelElements[0] corresponds to the first Fragment in ViewPager, modelElements[1] corresponds to the second Fragment and so on.
One way could be to use Bundle. But the way ViewPager creates Fragments doesn't expose me to the FragmentTransaction.
class MyViewPagerAdapter extends FragmentStatePagerAdapter {
public MyViewPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int i) {
if (i == 0) {
return MyFragment.newInstance();
} else if (i == 1) {
return MyFragment.newInstance();
} else if (i == 2) {
return MyFragment.newInstance();
}
return null;
}
}
What is the best way to pass the data?
Also, this sounds stupid to me - create a new instance of the same Fragment for each set of different data. Is there some other construct that Android offers that will fit my requirement?
1) Make your Model implement Parcelable interface.
2) Pass your model to newInstance() so it becomes newInstance(Model model)
3) In the newInstance() create bundle and set the argument for your fragment with a value of model
4) Retrieve the model from onCreate of the fragment
Below is Demo code i hv created to load different url in viewpage dynamically , however the there are dynamic urls passed to eachview in viewpage like below :
// ... do other initialization, such as create an ActionBar ...
pagerAdapter = new MainPagerAdapter();
pager = (ViewPager) findViewById(R.id.view_pager);
pager.setAdapter(pagerAdapter);
WebView w1 = new WebView(MainActivity.this);
w1.setWebViewClient(new WebViewClient());
w1.loadUrl("http://www.google.com");
WebView w2 = new WebView(MainActivity.this);
w2.setWebViewClient(new WebViewClient());
w2.loadUrl("http://www.live.com");
pagerAdapter.addView(w1, 0);
pagerAdapter.addView(w2, 1);
COMPLETE DEMO CODE
I have problem making my fragments communicating with each other through the Activity, which is using the FragmentPagerAdapter, as a helper class that implements the management of tabs and all details of connecting a ViewPager with associated TabHost. I have implemented FragmentPagerAdapter just as same as it is provided by the Android sample project Support4Demos.
The main question is how can I get particular fragment from FragmentManager when I don't have neither Id or Tag? FragmentPagerAdapter is creating the fragments and auto generating the Id and Tags.
Summary of the problem
Note: In this answer I'm going to reference FragmentPagerAdapter and its source code. But the general solution should also apply to FragmentStatePagerAdapter.
If you're reading this you probably already know that FragmentPagerAdapter/FragmentStatePagerAdapter is meant to create Fragments for your ViewPager, but upon Activity recreation (whether from a device rotation or the system killing your App to regain memory) these Fragments won't be created again, but instead their instances retrieved from the FragmentManager. Now say your Activity needs to get a reference to these Fragments to do work on them. You don't have an id or tag for these created Fragments because FragmentPagerAdapter set them internally. So the problem is how to get a reference to them without that information...
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...
}
I have found answer on my question based on following post: reusing fragments in a fragmentpageradapter
Few things I have learned:
getItem(int position) in the FragmentPagerAdapter is rather misleading name of what this method actually does. It creates new fragments, not returning existing ones. In so meaning, the method should be renamed to something like createItem(int position) in the Android SDK. So this method does not help us getting fragments.
Based on explanation in the post support FragmentPagerAdapterholds reference to old fragments you should leave the creation of the fragments to the FragmentPagerAdapter and in so meaning you have no reference to the Fragments or their tags. If you have fragment tag though, you can easily retrieve reference to it from the FragmentManager by calling findFragmentByTag(). We need a way to find out tag of a fragment at given page position.
Solution
Add following helper method in your class to retrieve fragment tag and send it to the findFragmentByTag() method.
private String getFragmentTag(int viewPagerId, int fragmentPosition)
{
return "android:switcher:" + viewPagerId + ":" + fragmentPosition;
}
NOTE! This is identical method that FragmentPagerAdapter use when creating new fragments. See this link http://code.google.com/p/openintents/source/browse/trunk/compatibility/AndroidSupportV2/src/android/support/v2/app/FragmentPagerAdapter.java#104
you don't need to override instantiateItem nor rely on compatibility with internal makeFragmentName method by manually creating fragment tags .
instantiateItem is a public method so you can call it in onCreate method of your activity surrounded with calls to startUpdate and finishUpdate methods as described in PagerAdapter javadoc:
A call to the PagerAdapter method startUpdate(ViewGroup) indicates that the contents of the ViewPager are about to change. One or more calls to instantiateItem(ViewGroup, int) and/or destroyItem(ViewGroup, int, Object) will follow, and the end of an update will be signaled by a call to finishUpdate(ViewGroup).
You can then by the way of the above, store references to instances of your fragments on local vars if you need. See example:
public class MyActivity extends AppCompatActivity {
Fragment0 tab0; Fragment1 tab1;
#Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.myLayout);
ViewPager viewPager = (ViewPager) findViewById(R.id.myViewPager);
MyPagerAdapter adapter = new MyPagerAdapter(getSupportFragmentManager());
viewPager.setAdapter(adapter);
((TabLayout) findViewById(R.id.tabs)).setupWithViewPager(viewPager);
adapter.startUpdate(viewPager);
tab0 = (Fragment0) adapter.instantiateItem(viewPager, 0);
tab1 = (Fragment1) adapter.instantiateItem(viewPager, 1);
adapter.finishUpdate(viewPager);
}
class MyPagerAdapter extends FragmentPagerAdapter {
public MyPagerAdapter(FragmentManager manager) {super(manager);}
#Override public int getCount() {return 2;}
#Override public Fragment getItem(int position) {
if (position == 0) return new Fragment0();
if (position == 1) return new Fragment1();
return null; // or throw some exception
}
#Override public CharSequence getPageTitle(int position) {
if (position == 0) return getString(R.string.tab0);
if (position == 1) return getString(R.string.tab1);
return null; // or throw some exception
}
}
}
instantiateItem will first try to get references to existing fragment instances from FragmentManager. Only if they don't exist yet, it will create new ones using getItem method from your adapter and "store" them in the FragmentManager for any future use.
UPDATE 05/2022: according to this comment the below part may lead to suboptimal performance in the current implementation.
Following the above javadoc, you still should call instantiateItem for all your tabs surrounded by startUpdate/finishUpdate in your onCreate method even if you don't need to obtain references to your fragments:
adapter.startUpdate(viewPager);
// ignoring return values of the below 2 calls, just side effects matter:
adapter.instantiateItem(viewPager, 0);
adapter.instantiateItem(viewPager, 1);
adapter.finishUpdate(viewPager);
If you don't do so, then you are risking that your fragment instances will never be committed to FragmentManager : when your activity becomes foreground instantiateItem will be called automatically to obtain your fragments, but startUpdate/finishUpdate may not (depending on implementation details) and what they basically do is begin/commit a FragmentTransaction.
This may result in references to the created fragment instances being lost very quickly (for example when you rotate your screen) and recreated much more often than necessary. Depending on how "heavy" your fragments are, it may have a non-negligible performance consequences.
Moreover, in such case instances of fragments stored on local vars may become stale: if android platform tries to obtain them from FragmentManager for whatever reason, it will fail and thus will create and use new ones, while your vars will still be referencing the old ones.
The way I did it is define an Hashtable of WeakReferences as follows:
protected Hashtable<Integer, WeakReference<Fragment>> fragmentReferences;
Then I wrote the getItem() method like this:
#Override
public Fragment getItem(int position) {
Fragment fragment;
switch(position) {
case 0:
fragment = new MyFirstFragmentClass();
break;
default:
fragment = new MyOtherFragmentClass();
break;
}
fragmentReferences.put(position, new WeakReference<Fragment>(fragment));
return fragment;
}
Then you can write a method:
public Fragment getFragment(int fragmentId) {
WeakReference<Fragment> ref = fragmentReferences.get(fragmentId);
return ref == null ? null : ref.get();
}
This seems to work well and I find it a little less hacky than the
"android:switcher:" + viewId + ":" + position
trick, as it does not rely on how the FragmentPagerAdapter is implemented.
Of course if the fragment has been released by the FragmentPagerAdapter or if it has not been yet created, getFragment will return null.
If anybody finds something wrong with this approach, comments are more than welcome.
I created this method which is working for me to get a reference to the current fragment.
public static Fragment getCurrentFragment(ViewPager pager, FragmentPagerAdapter adapter) {
try {
Method m = adapter.getClass().getSuperclass().getDeclaredMethod("makeFragmentName", int.class, long.class);
Field f = adapter.getClass().getSuperclass().getDeclaredField("mFragmentManager");
f.setAccessible(true);
FragmentManager fm = (FragmentManager) f.get(adapter);
m.setAccessible(true);
String tag = null;
tag = (String) m.invoke(null, pager.getId(), (long) pager.getCurrentItem());
return fm.findFragmentByTag(tag);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
return null;
}
FragmentStateAdapter for ViewPager2 UPDATE
FragmentStateAdapter has createFragment() instead of getItem(). And there is no such method as instantiateView().
So when the hosting Activity/Fragment is recreated after the configuration change, createFragment() doesn't get called. This means that fragments in the adapter are not created again, but instead their instances are retrieved from FragmentManager.
So if you need to do some work on the fragments, you can simply get them from FragmentManager.
Adapter creation:
CustomFragmentAdapter adapter = new CustomFragmentAdapter(getChildFragmentManager(), getLifecycle());
Retrieving fragments from FragmentManager after Activity/Fragment reload:
FragmentManager manager = getChildFragmentManager();
ArrayList<Fragment> fragments = new ArrayList<>(manager.getFragments());
for (Fragment fr : fragments) {
// do something
}
the solution suggested by #personne3000 is nice, but it has one problem: when activity goes to the background and gets killed by the system (in order to get some free memory) and then restored, the fragmentReferences will be empty, because getItem wouldn't be called.
The class below handles such situation:
public abstract class AbstractHolderFragmentPagerAdapter<F extends Fragment> extends FragmentPagerAdapter {
public static final String FRAGMENT_SAVE_PREFIX = "holder";
private final FragmentManager fragmentManager; // we need to store fragment manager ourselves, because parent's field is private and has no getters.
public AbstractHolderFragmentPagerAdapter(FragmentManager fm) {
super(fm);
fragmentManager = fm;
}
private SparseArray<WeakReference<F>> holder = new SparseArray<WeakReference<F>>();
protected void holdFragment(F fragment) {
holdFragment(holder.size(), fragment);
}
protected void holdFragment(int position, F fragment) {
if (fragment != null)
holder.put(position, new WeakReference<F>(fragment));
}
public F getHoldedItem(int position) {
WeakReference<F> ref = holder.get(position);
return ref == null ? null : ref.get();
}
public int getHolderCount() {
return holder.size();
}
#Override
public void restoreState(Parcelable state, ClassLoader loader) { // code inspired by Google's FragmentStatePagerAdapter implementation
super.restoreState(state, loader);
Bundle bundle = (Bundle) state;
for (String key : bundle.keySet()) {
if (key.startsWith(FRAGMENT_SAVE_PREFIX)) {
int index = Integer.parseInt(key.substring(FRAGMENT_SAVE_PREFIX.length()));
Fragment f = fragmentManager.getFragment(bundle, key);
holdFragment(index, (F) f);
}
}
}
#Override
public Parcelable saveState() {
Bundle state = (Bundle) super.saveState();
if (state == null)
state = new Bundle();
for (int i = 0; i < holder.size(); i++) {
int id = holder.keyAt(i);
final F f = getHoldedItem(i);
String key = FRAGMENT_SAVE_PREFIX + id;
fragmentManager.putFragment(state, key, f);
}
return state;
}
}
The main road block with getting a handle to the fragments is you can not rely on getItem(). After an orientation change, references to the fragments will be null and getItem() is not called again.
Here's an approach that does not rely upon the implementation of FragmentPagerAdapter to get the tag. Override instantiateItem() which will return the fragment created from getItem() or found from the fragment manager.
#Override
public Object instantiateItem(ViewGroup container, int position) {
Object value = super.instantiateItem(container, position);
if (position == 0) {
someFragment = (SomeFragment) value;
} else if (position == 1) {
anotherFragment = (AnotherFragment) value;
}
return value;
}
See this post on returning fragments from the FragmentPagerAdapter. Does rely on you knowing the index of your fragment - but this would be set in getItem() (at instantiation only)
I managed to solve this issue by using ids instead of tags. (I am using I defined FragmentStatePagerAdapter which uses my custom Fragments in which I overrode the onAttach method, where you save the id somewhere:
#Override
public void onAttach(Context context){
super.onAttach(context);
MainActivity.fragId = getId();
}
And then you just access the fragment easily inside the activity:
Fragment f = getSupportFragmentManager.findFragmentById(fragId);
I don't know if this is the best approach but nothing else worked for me.
All other options including getActiveFragment returned null or caused the app to crash.
I noticed that on screen rotation the fragment was being attached so I used it to send the fragment back to the activity.
In the fragment:
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mListener = (OnListInteractionListener) activity;
mListener.setListFrag(this);
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnFragmentInteractionListener");
}
}
Then in the activity:
#Override
public void setListFrag(MyListFragment lf) {
if (mListFragment == null) {
mListFragment = lf;
}
}
And finally in activity onCreate():
if (savedInstanceState != null) {
if (mListFragment != null)
mListFragment.setListItems(items);
}
This approach attaches the actual visible fragment to the activity without creating a new one.
Not sure if my method was the correct or best way to do this since I am a relative beginner with Java/Android, but it did work (I'm sure it violates object oriented principles but no other solution worked for my use case).
I had a hosting Activity that was using a ViewPager with a FragmentStatePagerAdapter. In order to get references to the Fragments that were created by FragmentStatePagerAdapter I created a callback interface within the fragment class:
public interface Callbacks {
public void addFragment (Fragment fragment);
public void removeFragment (Fragment fragment);
}
In the hosting activity I implemented the interface and created a LinkedHasSet to keep track of the fragments:
public class HostingActivity extends AppCompatActivity implements ViewPagerFragment.Callbacks {
private LinkedHashSet<Fragment> mFragments = new LinkedHashSet<>();
#Override
public void addFragment (Fragment fragment) {
mFragments.add(fragment);
}
#Override
public void removeFragment (Fragment fragment) {
mFragments.remove(fragment);
}
}
Within the ViewPagerFragment class I added the fragments to the list within onAttach and removed them within onDetach:
public class ViewPagerFragment extends Fragment {
private Callbacks mCallbacks;
public interface Callbacks {
public void addFragment (Fragment fragment);
public void removeFragment (Fragment fragment);
}
#Override
public void onAttach (Context context) {
super.onAttach(context);
mCallbacks = (Callbacks) context;
// Add this fragment to the HashSet in the hosting activity
mCallbacks.addFragment(this);
}
#Override
public void onDetach() {
super.onDetach();
// Remove this fragment from the HashSet in the hosting activity
mCallbacks.removeFragment(this);
mCallbacks = null;
}
}
Within the hosting activity you'll now be able to use mFragments to iterate through the fragments that currently exist in the FragmentStatePagerAdapter.
This class do the trick without relying on internal tags. Warning: Fragments should be accessed using the getFragment method and not the getItem one.
public class ViewPagerAdapter extends FragmentPagerAdapter {
private final Map<Integer, Reference<Fragment>> fragments = new HashMap<>();
private final List<Callable0<Fragment>> initializers = new ArrayList<>();
private final List<String> titles = new ArrayList<>();
public ViewPagerAdapter(FragmentManager fm) {
super(fm);
}
void addFragment(Callable0<Fragment> initializer, String title) {
initializers.add(initializer);
titles.add(title);
}
public Optional<Fragment> getFragment(int position) {
return Optional.ofNullable(fragments.get(position).get());
}
#Override
public Fragment getItem(int position) {
Fragment fragment = initializers.get(position).execute();
return fragment;
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
Fragment fragment = (Fragment) super.instantiateItem(container, position);
fragments.put(position, new WeakReference<>(fragment));
return fragment;
}
#Override
public int getCount() {
return initializers.size();
}
#Override
public CharSequence getPageTitle(int position) {
return titles.get(position);
}
}
Just go on try this code,
public class MYFragmentPAdp extends FragmentPagerAdapter {
public MYFragmentPAdp(FragmentManager fm) {
super(fm);
}
#Override
public int getCount() {
return 2;
}
#Override
public Fragment getItem(int position) {
if (position == 0)
Fragment fragment = new Fragment1();
else (position == 1)
Fragment fragment = new Fragment2();
return fragment;
}
}
I have this structure:
Activity A has a ViewPager. The pages of a ViewPager are as known Fragments.
Activity A has a list of complex objects, which I want to display in the pages.
When the user clicks on an item inside the ViewPager activity B must be started. Activity B also needs the list with complex objects.
So I have to pass this list
A -> Fragment(s) -> B
First I found out that it's not possible to use the fragment's constructor to pass data to it. I have to use parcelable or serializable. In the fragment I then have to parcel or serialize again to pass to B.
But I don't want to serialize or parcel whole list of complex data 2 times only to get it through to B.
I would like to avoid using static fields.
Then I came to the idea to pass a listener to the fragment, which listens to item click, and notifies my activity A and activity A then starts activity B with the data. This looks cleanest for me because A is actually the one the data belongs to, so A starts fragment with data, fragment comes back to A and then A starts B with data. I don't have to pass the data everywhere (serializing!).
But this doesn't seem to work, because how do I pass a listener to the fragment using serialization? What is wrong about using a listener in a fragment to come back to the activity?
How do I solve this?
Edit
I found this solution in a thread about dialog fragments: Get data back from a fragment dialog - best practices?
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mListener = (OnArticleSelectedListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement OnArticleSelectedListener");
}
}
So I add this to my fragment and make my activity implement certain interface.
This would solve the problem of passing data from fragments to B. But I still need to pass the list from A to the fragments. Probably I'll have to use a Parcelable...
Edit: I tried Parcelable, it works. But the objects in my list have a lot of fields between them also maps, for example. Writing the Parcelable code (write / read) is a pain. Maybe I just stick to static data...
The listener solution you found is excellent and actually recommended by Google (http://developer.android.com/training/basics/fragments/communicating.html).
As for your remaining problem of passing data to fragments, are you sure you need the entire list passed, and not just an element of the list for each fragment in the ViewPager? If you just need an element, you can do something like this:
/*
* An adapter for swiping through the fragments we have.
*/
private class CustomPagerAdapter extends FragmentStatePagerAdapter {
List<Thing> things;
public CustomPagerAdapter(FragmentManager fragmentManager, List<Thing> things) {
super(fragmentManager);
this.things = things;
}
#Override
public int getCount() {
return things.size();
}
#Override
public Fragment getItem(int position) {
return ThingFragment.newInstance(things.get(position));
}
}
/*
* Loads a view based on the thing.
*/
private static class ThingFragment extends Fragment {
private String name;
static ThingFragment newInstance(Thing thing) {
ThingFragment f = new ThingFragment();
// add some arguments to our fragment for onCreateView
Bundle args = new Bundle();
args.putString("name", thing.getName());
f.setArguments(args);
return f;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
name = getArguments().getString("name");
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.thing_fragment_layout, container, false);
TextView t = (TextView)v.findViewById(R.id.thingText);
t.setText(name);
return v;
}
}
And pass your list to the adapter. Of course, this requires that your dynamics be created dynamically, but inflating xml fragments is really easy.
If you actually DO need to pass the entire list, yeah, you might have to make the individual complex object Parcelable, and then add it to your Fragment args like with Bundle.putParcelableArray().
I'm using a ViewPager together with a FragmentStatePagerAdapter to host three different fragments:
[Fragment1]
[Fragment2]
[Fragment3]
When I want to get Fragment1 from the ViewPager in the FragmentActivity.
What is the problem, and how do I fix it?
The main answer relies on a name being generated by the framework. If that ever changes, then it will no longer work.
What about this solution, overriding instantiateItem() and destroyItem() of your Fragment(State)PagerAdapter:
public class MyPagerAdapter extends FragmentStatePagerAdapter {
SparseArray<Fragment> registeredFragments = new SparseArray<Fragment>();
public MyPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public int getCount() {
return ...;
}
#Override
public Fragment getItem(int position) {
return MyFragment.newInstance(...);
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
Fragment fragment = (Fragment) super.instantiateItem(container, position);
registeredFragments.put(position, fragment);
return fragment;
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
registeredFragments.remove(position);
super.destroyItem(container, position, object);
}
public Fragment getRegisteredFragment(int position) {
return registeredFragments.get(position);
}
}
This seems to work for me when dealing with Fragments that are available. Fragments that have not yet been instantiated, will return null when calling getRegisteredFragment. But I've been using this mostly to get the current Fragment out of the ViewPager: adapater.getRegisteredFragment(viewPager.getCurrentItem()) and this won't return null.
I'm not aware of any other drawbacks of this solution. If there are any, I'd like to know.
For grabbing fragments out of a ViewPager there are a lot of answers on here and on other related SO threads / blogs. Everyone I have seen is broken, and they generally seem to fall into one of the two types listed below. There are some other valid solutions if you only want to grab the current fragment, like this other answer on this thread.
If using FragmentPagerAdapter see below. If using FragmentStatePagerAdapter its worth looking at this. Grabbing indexes that are not the current one in a FragmentStateAdapter is not as useful as by the nature of it these will be completely torn down went out of view / out of offScreenLimit bounds.
THE UNHAPPY PATHS
Wrong: Maintain your own internal list of fragments, added to when FragmentPagerAdapter.getItem() is called
Usually using a SparseArray or Map
Not one of the many examples I have seen accounts for lifecycle events so this solution is fragile. As getItem is only called the first time a page is scrolled to (or obtained if your ViewPager.setOffscreenPageLimit(x) > 0) in the ViewPager, if the hosting Activity / Fragment is killed or restarted then the internal SpaseArray will be wiped out when the custom FragmentPagerActivity is recreated, but behind the scenes the ViewPagers internal fragments will be recreated, and getItem will NOT be called for any of the indexes, so the ability to get a fragment from index will be lost forever. You can account for this by saving out and restoring these fragment references via FragmentManager.getFragment() and putFragment but this starts to get messy IMHO.
Wrong: Construct your own tag id matching what is used under the hood in FragmentPagerAdapter and use this to retrieve the page Fragments from the FragmentManager
This is better insomuch as it copes with the losing-fragment-references problem in the first internal-array solution, but as rightly pointed out in the answers above and elsewhere on the net - it feels hacky as its a private method internal to ViewPager that could change at any time or for any OS version.
The method thats recreated for this solution is
private static String makeFragmentName(int viewId, long id) {
return "android:switcher:" + viewId + ":" + id;
}
A HAPPY PATH: ViewPager.instantiateItem()
A similar approach to getItem() above but non-lifecycle-breaking is to this is to hook into instantiateItem() instead of getItem() as the former will be called everytime that index is created / accessed. See this answer
A HAPPY PATH: Construct your own FragmentViewPager
Construct your own FragmentViewPager class from the source of the latest support lib and change the method used internally to generate the fragment tags. You can replace it with the below. This has the advantage that you know the tag creation will never change and your not relying on a private api / method, which is always dangerous.
/**
* #param containerViewId the ViewPager this adapter is being supplied to
* #param id pass in getItemId(position) as this is whats used internally in this class
* #return the tag used for this pages fragment
*/
public static String makeFragmentName(int containerViewId, long id) {
return "android:switcher:" + containerViewId + ":" + id;
}
Then as the doc says, when you want to grab a fragment used for an index just call something like this method (which you can put in the custom FragmentPagerAdapter or a subclass) being aware the result may be null if getItem has not yet been called for that page i.e. its not been created yet.
/**
* #return may return null if the fragment has not been instantiated yet for that position - this depends on if the fragment has been viewed
* yet OR is a sibling covered by {#link android.support.v4.view.ViewPager#setOffscreenPageLimit(int)}. Can use this to call methods on
* the current positions fragment.
*/
public #Nullable Fragment getFragmentForPosition(int position)
{
String tag = makeFragmentName(mViewPager.getId(), getItemId(position));
Fragment fragment = getSupportFragmentManager().findFragmentByTag(tag);
return fragment;
}
This is a simple solution and solves the issues in the other two solutions found everywhere on the web
Add next methods to your FragmentPagerAdapter:
public Fragment getActiveFragment(ViewPager container, int position) {
String name = makeFragmentName(container.getId(), position);
return mFragmentManager.findFragmentByTag(name);
}
private static String makeFragmentName(int viewId, int index) {
return "android:switcher:" + viewId + ":" + index;
}
getActiveFragment(0) has to work.
Here is the solution implemented into ViewPager https://gist.github.com/jacek-marchwicki/d6320ba9a910c514424d. If something fail you will see good crash log.
Another simple solution:
public class MyPagerAdapter extends FragmentPagerAdapter {
private Fragment mCurrentFragment;
public Fragment getCurrentFragment() {
return mCurrentFragment;
}
//...
#Override
public void setPrimaryItem(ViewGroup container, int position, Object object) {
if (getCurrentFragment() != object) {
mCurrentFragment = ((Fragment) object);
}
super.setPrimaryItem(container, position, object);
}
}
I know this has a few answers, but maybe this will help someone. I have used a relatively simple solution when I needed to get a Fragment from my ViewPager. In your Activity or Fragment holding the ViewPager, you can use this code to cycle through every Fragment it holds.
FragmentPagerAdapter fragmentPagerAdapter = (FragmentPagerAdapter) mViewPager.getAdapter();
for(int i = 0; i < fragmentPagerAdapter.getCount(); i++) {
Fragment viewPagerFragment = fragmentPagerAdapter.getItem(i);
if(viewPagerFragment != null) {
// Do something with your Fragment
// Check viewPagerFragment.isResumed() if you intend on interacting with any views.
}
}
If you know the position of your Fragment in the ViewPager, you can just call getItem(knownPosition).
If you don't know the position of your Fragment in the ViewPager, you can have your children Fragments implement an interface with a method like getUniqueId(), and use that to differentiate them. Or you can cycle through all Fragments and check the class type, such as if(viewPagerFragment instanceof FragmentClassYouWant)
!!! EDIT !!!
I have discovered that getItem only gets called by a FragmentPagerAdapter when each Fragment needs to be created the first time, after that, it appears the the Fragments are recycled using the FragmentManager. This way, many implementations of FragmentPagerAdapter create new Fragments in getItem. Using my above method, this means we will create new Fragments each time getItem is called as we go through all the items in the FragmentPagerAdapter. Due to this, I have found a better approach, using the FragmentManager to get each Fragment instead (using the accepted answer). This is a more complete solution, and has been working well for me.
FragmentPagerAdapter fragmentPagerAdapter = (FragmentPagerAdapter) mViewPager.getAdapter();
for(int i = 0; i < fragmentPagerAdapter.getCount(); i++) {
String name = makeFragmentName(mViewPager.getId(), i);
Fragment viewPagerFragment = getChildFragmentManager().findFragmentByTag(name);
// OR Fragment viewPagerFragment = getFragmentManager().findFragmentByTag(name);
if(viewPagerFragment != null) {
// Do something with your Fragment
if (viewPagerFragment.isResumed()) {
// Interact with any views/data that must be alive
}
else {
// Flag something for update later, when this viewPagerFragment
// returns to onResume
}
}
}
And you will need this method.
private static String makeFragmentName(int viewId, int position) {
return "android:switcher:" + viewId + ":" + position;
}
For my case, none of the above solutions worked.
However since I am using the Child Fragment Manager in a Fragment, the following was used:
Fragment f = getChildFragmentManager().getFragments().get(viewPager.getCurrentItem());
This will only work if your fragments in the Manager correspond to the viewpager item.
In order to get current Visible fragment from ViewPager. I am using this simple statement and it's working fine.
public Fragment getFragmentFromViewpager()
{
return ((Fragment) (mAdapter.instantiateItem(mViewPager, mViewPager.getCurrentItem())));
}
I handled it by first making a list of all the fragments (List<Fragment> fragments;) that I was going to use then added them to the pager making it easier to handle the currently viewed fragment.
So:
#Override
onCreate(){
//initialise the list of fragments
fragments = new Vector<Fragment>();
//fill up the list with out fragments
fragments.add(Fragment.instantiate(this, MainFragment.class.getName()));
fragments.add(Fragment.instantiate(this, MenuFragment.class.getName()));
fragments.add(Fragment.instantiate(this, StoresFragment.class.getName()));
fragments.add(Fragment.instantiate(this, AboutFragment.class.getName()));
fragments.add(Fragment.instantiate(this, ContactFragment.class.getName()));
//Set up the pager
pager = (ViewPager)findViewById(R.id.pager);
pager.setAdapter(new MyFragmentPagerAdapter(getSupportFragmentManager(), fragments));
pager.setOffscreenPageLimit(4);
}
so then this can be called:
public Fragment getFragment(ViewPager pager){
Fragment theFragment = fragments.get(pager.getCurrentItem());
return theFragment;
}
so then i could chuck it in an if statement that would only run if it was on the correct fragment
Fragment tempFragment = getFragment();
if(tempFragment == MyFragmentNo2.class){
MyFragmentNo2 theFrag = (MyFragmentNo2) tempFragment;
//then you can do whatever with the fragment
theFrag.costomFunction();
}
but thats just my hack and slash approach but it worked for me, I use it do do relevent changes to my currently displayed fragment when the back button is pushed.
This is based on Steven's answer above. This will return actual instance of the fragment which is already attached to the parent activity.
FragmentPagerAdapter fragmentPagerAdapter = (FragmentPagerAdapter) mViewPager.getAdapter();
for(int i = 0; i < fragmentPagerAdapter.getCount(); i++) {
Fragment viewPagerFragment = (Fragment) mViewPager.getAdapter().instantiateItem(mViewPager, i);
if(viewPagerFragment != null && viewPagerFragment.isAdded()) {
if (viewPagerFragment instanceof FragmentOne){
FragmentOne oneFragment = (FragmentOne) viewPagerFragment;
if (oneFragment != null){
oneFragment.update(); // your custom method
}
} else if (viewPagerFragment instanceof FragmentTwo){
FragmentTwo twoFragment = (FragmentTwo) viewPagerFragment;
if (twoFragment != null){
twoFragment.update(); // your custom method
}
}
}
}
I couldn't find a simple, clean way to do this. However, the ViewPager widget is just another ViewGroup , which hosts your fragments. The ViewPager has these fragments as immediate children. So you could just iterate over them (using .getChildCount() and .getChildAt() ), and see if the fragment instance that you're looking for is currently loaded into the ViewPager and get a reference to it. E.g. you could use some static unique ID field to tell the fragments apart.
Note that the ViewPager may not have loaded the fragment you're looking for since it's a virtualizing container like ListView.
FragmentPagerAdapter is the factory of the fragments. To find a fragment based on its position if still in memory use this:
public Fragment findFragmentByPosition(int position) {
FragmentPagerAdapter fragmentPagerAdapter = getFragmentPagerAdapter();
return getSupportFragmentManager().findFragmentByTag(
"android:switcher:" + getViewPager().getId() + ":"
+ fragmentPagerAdapter.getItemId(position));
}
Sample code for v4 support api.
You don't need to call getItem() or some other method at later stage to get the reference of a Fragment hosted inside ViewPager. If you want to update some data inside Fragment then use this approach: Update ViewPager dynamically?
Key is to set new data inside Adaper and call notifyDataSetChanged() which in turn will call getItemPosition(), passing you a reference of your Fragment and giving you a chance to update it. All other ways require you to keep reference to yourself or some other hack which is not a good solution.
#Override
public int getItemPosition(Object object) {
if (object instanceof UpdateableFragment) {
((UpdateableFragment) object).update(xyzData);
}
//don't return POSITION_NONE, avoid fragment recreation.
return super.getItemPosition(object);
}
Must extends FragmentPagerAdapter into your ViewPager adapter class.
If you use FragmentStatePagerAdapter then you will not able to find your Fragment by its ID
public static String makeFragmentName(int viewPagerId, int index) {
return "android:switcher:" + viewPagerId + ":" + index;
}
How to use this method :-
Fragment mFragment = ((FragmentActivity) getContext()).getSupportFragmentManager().findFragmentByTag(
AppMethodUtils.makeFragmentName(mViewPager.getId(), i)
);
InterestViewFragment newFragment = (InterestViewFragment) mFragment;
Hey I have answered this question here. Basically, you need to override
public Object instantiateItem(ViewGroup container, int position)
method of FragmentStatePagerAdapter.
Best solution is to use the extension we created at CodePath called SmartFragmentStatePagerAdapter. Following that guide, this makes retrieving fragments and the currently selected fragment from a ViewPager significantly easier. It also does a better job of managing the memory of the fragments embedded within the adapter.
The easiest and the most concise way. If all your fragments in ViewPager are of different classes you may retrieve and distinguish them as following:
public class MyActivity extends Activity
{
#Override
public void onAttachFragment(Fragment fragment) {
super.onAttachFragment(fragment);
if (fragment.getClass() == MyFragment.class) {
mMyFragment = (MyFragment) fragment;
}
}
}
I implemented this easy with a bit different approach.
My custom FragmentAdapter.getItem method returned not new MyFragment(), but the instance of MyFragment that was created in FragmentAdapter constructor.
In my activity I then got the fragment from the adapter, check if it is instanceOf needed Fragment, then cast and use needed methods.
Create integer resource id in /values/integers.xml
<integer name="page1">1</integer>
<integer name="page2">2</integer>
<integer name="page3">3</integer>
Then in PagerAdapter getItem function:
public Fragment getItem(int position) {
Fragment fragment = null;
if (position == 0) {
fragment = FragmentOne.newInstance();
mViewPager.setTag(R.integer.page1,fragment);
}
else if (position == 1) {
fragment = FragmentTwo.newInstance();
mViewPager.setTag(R.integer.page2,fragment);
} else if (position == 2) {
fragment = FragmentThree.newInstance();
mViewPager.setTag(R.integer.page3,fragment);
}
return fragment;
}
Then in activity write this function to get fragment reference:
private Fragment getFragmentByPosition(int position) {
Fragment fragment = null;
switch (position) {
case 0:
fragment = (Fragment) mViewPager.getTag(R.integer.page1);
break;
case 1:
fragment = (Fragment) mViewPager.getTag(R.integer.page2);
break;
case 2:
fragment = (Fragment) mViewPager.getTag(R.integer.page3);
break;
}
return fragment;
}
Get the fragment reference by calling the above function and then cast it to your custom fragment:
Fragment fragment = getFragmentByPosition(position);
if (fragment != null) {
FragmentOne fragmentOne = (FragmentOne) fragment;
}
Easy way to iterate over fragments in fragment manager. Find viewpager, that has section position argument, placed in public static PlaceholderFragment newInstance(int sectionNumber).
public PlaceholderFragment getFragmentByPosition(Integer pos){
for(Fragment f:getChildFragmentManager().getFragments()){
if(f.getId()==R.id.viewpager && f.getArguments().getInt("SECTNUM") - 1 == pos) {
return (PlaceholderFragment) f;
}
}
return null;
}
In Fragment
public int getArgument(){
return mPage;
{
public void update(){
}
In FragmentActivity
List<Fragment> fragments = getSupportFragmentManager().getFragments();
for(Fragment f:fragments){
if((f instanceof PageFragment)&&(!f.isDetached())){
PageFragment pf = (PageFragment)f;
if(pf.getArgument()==pager.getCurrentItem())pf.update();
}
}
in TabLayout there are multiple tab for Fragment. you can find the fragment by Tag using the index of the fragment.
For ex. the index for Fragment1 is 0, so in findFragmentByTag() method, pass the tag for the Viewpager.after using fragmentTransaction you can add,replace the fragment.
String tag = "android:switcher:" + R.id.viewPager + ":" + 0;
Fragment1 f = (Fragment1) getSupportFragmentManager().findFragmentByTag(tag);
Ok for the adapter FragmentStatePagerAdapter I fund a solution :
in your FragmentActivity :
ActionBar mActionBar = getSupportActionBar();
mActionBar.addTab(mActionBar.newTab().setText("TAB1").setTabListener(this).setTag(Fragment.instantiate(this, MyFragment1.class.getName())));
mActionBar.addTab(mActionBar.newTab().setText("TAB2").setTabListener(this).setTag(Fragment.instantiate(this, MyFragment2.class.getName())));
mActionBar.addTab(mActionBar.newTab().setText("TAB3").setTabListener(this).setTag(Fragment.instantiate(this, MyFragment3.class.getName())));
viewPager = (STViewPager) super.findViewById(R.id.viewpager);
mPagerAdapter = new MyPagerAdapter(getSupportFragmentManager(), mActionBar);
viewPager.setAdapter(this.mPagerAdapter);
and create a methode in your class FragmentActivity - So that method give you access to your Fragment, you just need to give it the position of the fragment you want:
public Fragment getActiveFragment(int position) {
String name = MyPagerAdapter.makeFragmentName(position);
return getSupportFragmentManager().findFragmentByTag(name);
}
in your Adapter :
public class MyPagerAdapter extends FragmentStatePagerAdapter {
private final ActionBar actionBar;
private final FragmentManager fragmentManager;
public MyPagerAdapter(FragmentManager fragmentManager, com.actionbarsherlock.app.ActionBarActionBar mActionBar) {super(fragmentManager);
this.actionBar = mActionBar;
this.fragmentManager = fragmentManager;
}
#Override
public Fragment getItem(int position) {
getSupportFragmentManager().beginTransaction().add(mTchatDetailsFragment, makeFragmentName(position)).commit();
return (Fragment)this.actionBar.getTabAt(position);
}
#Override
public int getCount() {
return this.actionBar.getTabCount();
}
#Override
public CharSequence getPageTitle(int position) {
return this.actionBar.getTabAt(position).getText();
}
private static String makeFragmentName(int viewId, int index) {
return "android:fragment:" + index;
}
}
Fragment yourFragment = yourviewpageradapter.getItem(int index);
index is the place of fragment in adapter like you added fragment1 first so retreive fragment1 pass index as 0 and so on for rest