I'm populating view pager through fragments likes this
pagerAdapter = new PagerAdapter(getSupportFragmentManager());
mViewPager = (ViewPager) findViewById(R.id.container);
for (int i = 0; i < 10; i++) {
newfragment fragobj = new newfragment();
fragobj.setArguments(bundle);
pagerAdapter.addFragment(fragobj, "Category");
}
mViewPager.setAdapter(pagerAdapter);
mViewPager.setCurrentItem(page);
I want to clear all fragments and add new fragments after clearing. If I add new fragments now it ads on with old fragments.
I tried many stack overflow answers but none of them worked for me.
the fragment was already getting removed and destroyed. the issue was of viewpages.
Only this worked for me
myViewPager.setSaveFromParentEnabled(false);
Cheers !
Add this method in ViewPagerAdapter and call it when you want to clear fragments from viewPager
public void clear() {
FragmentTransaction transaction = manager.beginTransaction();
for (Fragment fragment : mFragmentList) {
transaction.remove(fragment);
}
mFragmentList.clear();
transaction.commitAllowingStateLoss();
}
mViewPager : is the view you are using to set you Fragment
mViewPager = (YourViewPager) findViewById(R.id.myPager);
TABLE : is just a Integer list of the position of all my Fragments
public void destroyAllItem() {
int mPosition = mViewPager.getCurrentItem();
int mPositionMax = mViewPager.getCurrentItem()+1;
if (TABLE.size() > 0 && mPosition < TABLE.size()) {
if (mPosition > 0) {
mPosition--;
}
for (int i = mPosition; i < mPositionMax; i++) {
try {
Object objectobject = this.instantiateItem(mViewPager, TABLE.get(i).intValue());
if (objectobject != null)
destroyItem(mViewPager, TABLE.get(i).intValue(), objectobject);
} catch (Exception e) {
Log.i(TAG, "no more Fragment in FragmentPagerAdapter");
}
}
}
}
#Override
public void destroyItem(ViewGroup container, int position, Object object) {
super.destroyItem(container, position, object);
if (position <= getCount()) {
FragmentManager manager = ((Fragment) object).getFragmentManager();
FragmentTransaction trans = manager.beginTransaction();
trans.remove((Fragment) object);
trans.commit();
}
}
With ViewPager2 (1.0.0) and FragmentStateAdapter I also found the current view not to be recreated after updating the data set using notifyDataSetChanged, even when the data corresponding to the current view is changed. Apparently, notifyDataSetChanged doesn't perform a very thorough investigation of what has been changed and assumes the current view to be still correct.
The workaround I found was to simply set the same adapter that I had already set for the previous data set again:
viewPager2.setAdapter(myFragmentStateAdapter);
Apparently, setting an adapter makes it flush the cached views, which makes very good sense of course. Luckily, it doesn't check whether it's the same adapter.
I use navController fragment and in one fragment I also had viewPager with few slides set in a "parent fragment, which was part of the nav tree".
Sadly after the parent fragment was destoryed, the frags created within it for view pager were not getting cleared and they were restored while resuming the app, even tho they couldn't be shown!!
I've set adapter to null in parent's fragment onDestroy()-. and it clears all frags used inside viewPager.
If you have similiar problem, this wokrs 100%.
Related
I'm working on an android application, that uses a navigation drawer to switch between two fragments. However, each time I switch, the fragment is completely recreated.
Here is the code from my main activity.
/* The click listener for ListView in the navigation drawer */
private class DrawerItemClickListener implements ListView.OnItemClickListener {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
selectItem(position);
}
}
private void selectItem(int position) {
android.support.v4.app.Fragment fragment;
String tag;
android.support.v4.app.FragmentManager; fragmentManager = getSupportFragmentManager();
switch(position) {
case 0:
if(fragmentManager.findFragmentByTag("one") != null) {
fragment = fragmentManager.findFragmentByTag("one");
} else {
fragment = new OneFragment();
}
tag = "one";
break;
case 1:
if(fragmentManager.findFragmentByTag("two") != null) {
fragment = fragmentManager.findFragmentByTag("two");
} else {
fragment = new TwoFragment();
}
tag = "two";
break;
}
fragment.setRetainInstance(true);
fragmentManager.beginTransaction().replace(R.id.container, fragment, tag).commit();
// update selected item and title, then close the drawer
mDrawerList.setItemChecked(position, true);
setTitle(mNavTitles[position]);
mDrawerLayout.closeDrawer(mDrawerList);
}
I've set up some debug logging, and every time selectItem is called, one fragment is destroyed, while the other is created.
Is there any way to prevent the fragments from being recreated, and just reuse them instead?
After #meredrica pointed out that replace() destroys the fragments, I went back through the FragmentManager documentation. This is the solution I've come up with, that seems to be working.
/* The click listener for ListView in the navigation drawer */
private class DrawerItemClickListener implements ListView.OnItemClickListener {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
selectItem(position);
}
}
private void selectItem(int position) {
android.support.v4.app.FragmentManager; fragmentManager = getSupportFragmentManager();
switch(position) {
case 0:
if(fragmentManager.findFragmentByTag("one") != null) {
//if the fragment exists, show it.
fragmentManager.beginTransaction().show(fragmentManager.findFragmentByTag("one")).commit();
} else {
//if the fragment does not exist, add it to fragment manager.
fragmentManager.beginTransaction().add(R.id.container, new OneFragment(), "one").commit();
}
if(fragmentManager.findFragmentByTag("two") != null){
//if the other fragment is visible, hide it.
fragmentManager.beginTransaction().hide(fragmentManager.findFragmentByTag("two")).commit();
}
break;
case 1:
if(fragmentManager.findFragmentByTag("two") != null) {
//if the fragment exists, show it.
fragmentManager.beginTransaction().show(fragmentManager.findFragmentByTag("two")).commit();
} else {
//if the fragment does not exist, add it to fragment manager.
fragmentManager.beginTransaction().add(R.id.container, new TwoFragment(), "two").commit();
}
if(fragmentManager.findFragmentByTag("one") != null){
//if the other fragment is visible, hide it.
fragmentManager.beginTransaction().hide(fragmentManager.findFragmentByTag("one")).commit();
}
break;
}
// update selected item and title, then close the drawer
mDrawerList.setItemChecked(position, true);
setTitle(mNavTitles[position]);
mDrawerLayout.closeDrawer(mDrawerList);
}
I also added this bit, but I'm not sure if it's necessary or not.
#Override
public void onDestroy() {
super.onDestroy();
FragmentManager fragmentManager = getSupportFragmentManager();
if(fragmentManager.findFragmentByTag("one") != null){
fragmentManager.beginTransaction().remove(fragmentManager.findFragmentByTag("one")).commit();
}
if(fragmentManager.findFragmentByTag("two") != null){
fragmentManager.beginTransaction().remove(fragmentManager.findFragmentByTag("two")).commit();
}
}
Use the attach/detach method with tags:
Detach will destroy the view hirachy but keeps the state, like if on the backstack; this will let the "not-visible" fragment have a smaller memory footprint. But mind you that you need to correctly implement the fragment lifecycle (which you should do in the first place)
Detach the given fragment from the UI. This is the same state as when it is put on the back stack: the fragment is removed from the UI, however its state is still being actively managed by the fragment manager. When going into this state its view hierarchy is destroyed.
The first time you add the fragment
FragmentTransaction t = getSupportFragmentManager().beginTransaction();
t.add(android.R.id.content, new MyFragment(),MyFragment.class.getSimpleName());
t.commit();
then you detach it
FragmentTransaction t = getSupportFragmentManager().beginTransaction();
t.detach(MyFragment.class.getSimpleName());
t.commit();
and attach it again if switched back, state will be kept
FragmentTransaction t = getSupportFragmentManager().beginTransaction();
t.attach(getSupportFragmentManager().findFragmentByTag(MyFragment.class.getSimpleName()));
t.commit();
But you always have to check if the fragment was added yet, if not then add it, else just attach it:
if (getSupportFragmentManager().findFragmentByTag(MyFragment.class.getSimpleName()) == null) {
FragmentTransaction t = getSupportFragmentManager().beginTransaction();
t.add(android.R.id.content, new MyFragment(), MyFragment.class.getSimpleName());
t.commit();
} else {
FragmentTransaction t = getSupportFragmentManager().beginTransaction();
t.attach(getSupportFragmentManager().findFragmentByTag(MyFragment.class.getSimpleName()));
t.commit();
}
The replace method destroys your fragments. One workaround is to set them to Visibility.GONE, another (less easy) method is to hold them in a variable. If you do that, make sure you don't leak memory left and right.
I did this before like this:
if (mPrevFrag != fragment) {
// Change
FragmentTransaction ft = fragmentManager.beginTransaction();
if (mPrevFrag != null){
ft.hide(mPrevFrag);
}
ft.show(fragment);
ft.commit();
mPrevFrag = fragment;
}
(you will need to track your pervious fragment in this solution)
I guess you can not directly manipulate the lifecycle mechanisms of your Fragments. The very fact that you can findFragmentByTag is not very bad. It means that the Fragment object is not recreated fully, if it is already commited. The existing Fragment just passes all the lifecycle steps each Fragment has - that means that only UI is "recreated".
It is a very convenient and useful memory management strategy - and appropriate, in most cases. Fragment which is gone, has the resources which have to be utilized in order to de-allocate memory.
If you just cease using this strategy, the memory usage of your application could increase badly.
Nonetheless, there are retained fragments, which lifecycle is a bit different and do not correspond to the Activity they are attached to. Typically, they are used to retain some things you want to save, for example, to manage configuration changes
However, the fragment [re]creation strategy depends on the context - that is, what you would like to solve, and what are the trade-offs that you are willing to accept.
Just find the current fragment calling getFragmentById("id of your container") and then hide it and show needed fragment.
private void openFragment(Fragment fragment, String tag) {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
Fragment existingFragment = fragmentManager.findFragmentByTag(tag);
if (existingFragment != null) {
Fragment currentFragment = fragmentManager.findFragmentById(R.id.container);
fragmentTransaction.hide(currentFragment);
fragmentTransaction.show(existingFragment);
}
else {
fragmentTransaction.add(R.id.container, fragment, tag);
}
fragmentTransaction.commit();
}
Same idea as Tester101 but this is what I ended up using.
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
Fragment oldFragment = fragmentManager.findFragmentByTag( "" + m_lastDrawerSelectPosition );
if ( oldFragment != null )
fragmentTransaction.hide( oldFragment );
Fragment newFragment = fragmentManager.findFragmentByTag( "" + position );
if ( newFragment == null )
{
newFragment = getFragment( position );
fragmentTransaction.add( R.id.home_content_frame, newFragment, "" + position );
}
fragmentTransaction.show( newFragment );
fragmentTransaction.commit();
Hide easily in kotlin using extensions:
fun FragmentManager.present(newFragment: Fragment, lastFragment: Fragment? = null, containerId: Int) {
if (lastFragment == newFragment) return
val transaction = beginTransaction()
if (lastFragment != null && findFragmentByTag(lastFragment.getTagg()) != null) {
transaction.hide(lastFragment)
}
val existingFragment = findFragmentByTag(newFragment.getTagg())
if (existingFragment != null) {
transaction.show(existingFragment).commit()
} else {
transaction.add(containerId, newFragment, newFragment.getTagg()).commit()
}
}
fun Fragment.getTagg(): String = this::class.java.simpleName
Usage
supportFragmentManager.present(fragment, lastFragment, R.id.fragmentPlaceHolder)
lastFragment = fragment
Here's what I'm using for a simple 2 fragment case in Kotlin:
private val advancedHome = HomeAdvancedFragment()
private val basicHome = HomeBasicFragment()
override fun onCreate(savedInstanceState: Bundle?) {
...
// Attach both fragments and hide one so we can swap out easily later
supportFragmentManager.commit {
setReorderingAllowed(true)
add(R.id.fragment_container_view, basicHome)
add(R.id.fragment_container_view, advancedHome)
hide(basicHome)
}
binding.displayModeToggle.onStateChanged {
when (it) {
0 -> swapFragments(advancedHome, basicHome)
1 -> swapFragments(basicHome, advancedHome)
}
}
...
}
With this FragmentActivity extension:
fun FragmentActivity.swapFragments(show: Fragment, hide: Fragment) {
supportFragmentManager.commit {
show(show)
hide(hide)
}
}
How about playing with the Visible attribute?
this is a little late response.
if you're using view pager for fragments, set the off screen page limit of the fragment to the number of fragments created.
mViewPager.setOffscreenPageLimit(3); // number of fragments here is 3
So this is a bit of a complex question, but I will try to make it as easy to follow as possible.
To start, I have a main activity with a view pager. There are 2 pages in it, with 2 actionbar tabs linking each page.
I also have a drawer that has 4 options, the first 2 are the 2 pages in the view pager, and the second 2 are pages that aren't. If you select one of the second 2, I configured my ViewPager to swap out the fragments with 2 other ones. All of these fragments are singletons, so that switching back and forth doesn't cause any memory issues.
On switching to the second set of fragments, one of the fragments has a frame in it that I use to swap out child fragments when a button is selected. This is where the problem lies. After that fragment loads the first time, it works fine. But if you use the drawer to switch to the first set, and then back to the second again, the child fragment is gone. It then tends to crash after you do anything else after that.
This is the crash log for it
01-10 15:55:05.272: E/MessageQueue-JNI(21034): java.lang.NullPointerException: Attempt to invoke virtual method 'void android.support.v4.app.FragmentManagerImpl.performPendingDeferredStart(android.support.v4.app.Fragment)' on a null object reference
If I play around a bit with the code , I get something like this. These two errors are the ones I seem to produce the most:
01-10 16:00:13.072: E/AndroidRuntime(22227): java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.ClassLoader android.support.v4.app.FragmentActivity.getClassLoader()' on a null object reference
I can continue to call replace() on the frame view, so it's not going away. Therefore I'm guessing that's not the issue. It
Here is some relevant code for it.
The Fragment's code:
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.calculator, container, false);
final FragmentManager fragmentManager = getChildFragmentManager();
if(afFrag == null && topFrag == null){
afFrag = CalculatorAfFragment.getInstance();
topFrag = CalculatorTopFragment.getInstance();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.calc_frame, afFrag, "afFrag").commit();
}
RadioGroup typeGroup = (RadioGroup) view.findViewById(R.id.type_buttons);
typeGroup.setOnCheckedChangeListener(new OnCheckedChangeListener()
{
public void onCheckedChanged(RadioGroup group, int checkedId) {
if (checkedId == R.id.af_button){
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.calc_frame, afFrag, "afFrag").commit();
}
else if (checkedId == R.id.top_button){
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.calc_frame, topFrag, "topFrag").commit();
}
}
});
return view;
The FragmentStatePagerAdapter code:
#Override
public Fragment getItem(int position) {
if(tools){
return toolsFragmentsList.get(position);
} else {
return guidesFragmentsList.get(position);
}
}
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
public void swapFragments(int position){
if (tools){
tools = false;
stores = false;
notifyDataSetChanged();
this.setPrimaryItem(mPager, position-1, getItem(position - 1));
} else {
tools = true;
notifyDataSetChanged();
this.setPrimaryItem(mPager, position-4, getItem(position - 4));
}
}
Try setting a unique ID on the ViewPager via mPager.setId(View.generateViewId()).
Please tell me how to reset the content of ViewPager on android.
I tried to call adapter.notifyDataSetChanged() but the adapter doesn't call getItem(position) when I scroll the view. It always returns old child views.
Update: This is my Fragment View. When I reset viewPager data, It used loaded Fragment views. It's just start at the onCreateView instead of Constructor.
public class ResultView extends Fragment {
//Declare properties
//Constructor
public ResultView(Province province, Calendar calendar) {
Log.d("xskt", "ResultView constructor");
this.province = province;
this.calendar = (Calendar) calendar.clone();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Log.d("xskt", "ResultView onCreateView");
View view = inflater.inflate(R.layout.resultview, null);
//do some thing
return view;
}
Thanks.
I have changed to use FragmentStatePagerAdapter instead of FragmentPagerAdapter, the problem is resolved. Hope this helps.
I think the best solution to reload fragments in another fragment is :
#Override
public void onPause() {
super.onResume();
List<Fragment> fragments = getFragmentManager().getFragments();
if (fragments != null) {
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
for (Fragment fragment : fragments) {
if (fragment instanceof Fragment) {
fragmentTransaction.remove(fragment);
}
}
fragmentTransaction.commitNowAllowingStateLoss();
}
}
Check for your use case whether commitNowAllowingStateLoss or commitAllowingStateLoss is necessary.
One method is to use the observer pattern. The data you are presenting is likely in an sqlite DB (or in stored in memory). You can set a listener in your fragment that will make the necessary updates to your fragment when the data is changed.
It works if you do the following:
invalidate the viewPager to invalidate the cache
call notifyDataSetChanged() to tell the adapter that there have been changes and it has to refresh the Fragments
override getItemPosition in your FragmentStatePagerAdapter in order to have all of your Fragments refreshed
In your Activity or wherever you want to refresh the data:
viewPager.invalidate();
fragmentStatePageAdapter.notifyDataSetChanged();
In you FragmentStatePageAdapter:
#Override
public int getItemPosition(#NotNull Object object) {
return POSITION_NONE;
}
I'm working on an application in which tabs are implemented using FragmentActivity. Since, tabs are required throughout the application, fragments are used extensively to make the application compatible on all the versions of android.
As a consequence, I'm facing a problem in visualizing as to what fragments are present on the backstack. I'm sure there is a way to retrieve the list of fragments present on the backstack. Thanks.
The FragmentManager has methods:
getBackStackEntryCount()
getBackStackEntryAt (int index)
FragmentManager fm = getFragmentManager();
for(int entry = 0; entry<fm.getBackStackEntryCount(); entry++){
Log.i(TAG, "Found fragment: " + fm.getBackStackEntryAt(entry).getId());
}
If you want to check which fragment is visible and if you know the id of view where fragment is placed, first you have to add below in onCreate()
getSupportFragmentManager().addOnBackStackChangedListener(new OnBackStackChangedListener() {
#Override
public void onBackStackChanged() {
Fragment f = getSupportFragmentManager().findFragmentById(R.id.content_frame);
if (f != null){
updateActionBarTitle(f);
}
}
});
private void updateActionBarTitle(Fragment fragment) {
String fragClassName = fragment.getClass().getName();
if (fragClassName.equals(FirstFragment.class.getName())) {
setTitle("Home");
} else if (fragClassName.equals(SecondFragment.class.getName())) {
setTitle("Second");
}
}
This will update your action bar title on back stack change listener.
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