Fragments onResume from back stack - android

I'm using the compatibility package to use Fragments with Android 2.2.
When using fragments, and adding transitions between them to the backstack, I'd like to achieve the same behavior of onResume of an activity, i.e., whenever a fragment is brought to "foreground" (visible to the user) after poping out of the backstack, I'd like some kind of callback to be activated within the fragment (to perform certain changes on a shared UI resource, for instance).
I saw that there is no built in callback within the fragment framework. is there s a good practice in order to achieve this?

For a lack of a better solution, I got this working for me:
Assume I have 1 activity (MyActivity) and few fragments that replaces each other (only one is visible at a time).
In MyActivity, add this listener:
getSupportFragmentManager().addOnBackStackChangedListener(getListener());
(As you can see I'm using the compatibility package).
getListener implementation:
private OnBackStackChangedListener getListener()
{
OnBackStackChangedListener result = new OnBackStackChangedListener()
{
public void onBackStackChanged()
{
FragmentManager manager = getSupportFragmentManager();
if (manager != null)
{
MyFragment currFrag = (MyFragment) manager.findFragmentById(R.id.fragmentItem);
currFrag.onFragmentResume();
}
}
};
return result;
}
MyFragment.onFragmentResume() will be called after a "Back" is pressed. few caveats though:
It assumes you added all
transactions to the backstack (using
FragmentTransaction.addToBackStack())
It will be activated upon each stack
change (you can store other stuff in
the back stack such as animation) so
you might get multiple calls for the
same instance of fragment.

I've changed the suggested solution a little bit. Works better for me like that:
private OnBackStackChangedListener getListener() {
OnBackStackChangedListener result = new OnBackStackChangedListener() {
public void onBackStackChanged() {
FragmentManager manager = getSupportFragmentManager();
if (manager != null) {
int backStackEntryCount = manager.getBackStackEntryCount();
if (backStackEntryCount == 0) {
finish();
}
Fragment fragment = manager.getFragments()
.get(backStackEntryCount - 1);
fragment.onResume();
}
}
};
return result;
}

After a popStackBack() you can use the following callback : onHiddenChanged(boolean hidden) within your fragment

The following section at Android Developers describes a communication mechanism Creating event callbacks to the activity. To quote a line from it:
A good way to do that is to define a callback interface inside the fragment and require that the host activity implement it. When the activity receives a callback through the interface, it can share the information with other fragments in the layout as necessary.
Edit:
The fragment has an onStart(...) which is invoked when the fragment is visible to the user. Similarly an onResume(...) when visible and actively running. These are tied to their activity counterparts.
In short: use onResume()

If a fragment is put on backstack, Android simply destroys its view. The fragment instance itself is not killed. A simple way to start should to to listen to the onViewCreated event, an put you "onResume()" logic there.
boolean fragmentAlreadyLoaded = false;
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onViewCreated(view, savedInstanceState);
if (savedInstanceState == null && !fragmentAlreadyLoaded) {
fragmentAlreadyLoaded = true;
// Code placed here will be executed once
}
//Code placed here will be executed even when the fragment comes from backstack
}

In my activity onCreate()
getSupportFragmentManager().addOnBackStackChangedListener(getListener());
Use this method to catch specific Fragment and call onResume()
private FragmentManager.OnBackStackChangedListener getListener()
{
FragmentManager.OnBackStackChangedListener result = new FragmentManager.OnBackStackChangedListener()
{
public void onBackStackChanged()
{
Fragment currentFragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
if (currentFragment instanceof YOURFRAGMENT) {
currentFragment.onResume();
}
}
};
return result;
}

A little improved and wrapped into a manager solution.
Things to keep in mind. FragmentManager is not a singleton, it manages only Fragments within Activity, so in every activity it will be new. Also, this solution so far doesn't take ViewPager into account that calls setUserVisibleHint() method helping to control visiblity of Fragments.
Feel free to use following classes when dealing with this issue (uses Dagger2 injection). Call in Activity:
//inject FragmentBackstackStateManager instance to myFragmentBackstackStateManager
FragmentManager fragmentManager = getSupportFragmentManager();
myFragmentBackstackStateManager.apply(fragmentManager);
FragmentBackstackStateManager.java:
#Singleton
public class FragmentBackstackStateManager {
private FragmentManager fragmentManager;
#Inject
public FragmentBackstackStateManager() {
}
private BackstackCallback backstackCallbackImpl = new BackstackCallback() {
#Override
public void onFragmentPushed(Fragment parentFragment) {
parentFragment.onPause();
}
#Override
public void onFragmentPopped(Fragment parentFragment) {
parentFragment.onResume();
}
};
public FragmentBackstackChangeListenerImpl getListener() {
return new FragmentBackstackChangeListenerImpl(fragmentManager, backstackCallbackImpl);
}
public void apply(FragmentManager fragmentManager) {
this.fragmentManager = fragmentManager;
fragmentManager.addOnBackStackChangedListener(getListener());
}
}
FragmentBackstackChangeListenerImpl.java:
public class FragmentBackstackChangeListenerImpl implements FragmentManager.OnBackStackChangedListener {
private int lastBackStackEntryCount = 0;
private final FragmentManager fragmentManager;
private final BackstackCallback backstackChangeListener;
public FragmentBackstackChangeListenerImpl(FragmentManager fragmentManager, BackstackCallback backstackChangeListener) {
this.fragmentManager = fragmentManager;
this.backstackChangeListener = backstackChangeListener;
lastBackStackEntryCount = fragmentManager.getBackStackEntryCount();
}
private boolean wasPushed(int backStackEntryCount) {
return lastBackStackEntryCount < backStackEntryCount;
}
private boolean wasPopped(int backStackEntryCount) {
return lastBackStackEntryCount > backStackEntryCount;
}
private boolean haveFragments() {
List<Fragment> fragmentList = fragmentManager.getFragments();
return fragmentList != null && !fragmentList.isEmpty();
}
/**
* If we push a fragment to backstack then parent would be the one before => size - 2
* If we pop a fragment from backstack logically it should be the last fragment in the list, but in Android popping a fragment just makes list entry null keeping list size intact, thus it's also size - 2
*
* #return fragment that is parent to the one that is pushed to or popped from back stack
*/
private Fragment getParentFragment() {
List<Fragment> fragmentList = fragmentManager.getFragments();
return fragmentList.get(Math.max(0, fragmentList.size() - 2));
}
#Override
public void onBackStackChanged() {
int currentBackStackEntryCount = fragmentManager.getBackStackEntryCount();
if (haveFragments()) {
Fragment parentFragment = getParentFragment();
//will be null if was just popped and was last in the stack
if (parentFragment != null) {
if (wasPushed(currentBackStackEntryCount)) {
backstackChangeListener.onFragmentPushed(parentFragment);
} else if (wasPopped(currentBackStackEntryCount)) {
backstackChangeListener.onFragmentPopped(parentFragment);
}
}
}
lastBackStackEntryCount = currentBackStackEntryCount;
}
}
BackstackCallback.java:
public interface BackstackCallback {
void onFragmentPushed(Fragment parentFragment);
void onFragmentPopped(Fragment parentFragment);
}

This is the correct answer you can call onResume() providing the fragment is attached to the activity. Alternatively you can use onAttach and onDetach

onResume() for the fragment works fine...
public class listBook extends Fragment {
private String listbook_last_subtitle;
...
#Override
public void onCreate(Bundle savedInstanceState) {
String thisFragSubtitle = (String) getActivity().getActionBar().getSubtitle();
listbook_last_subtitle = thisFragSubtitle;
}
...
#Override
public void onResume(){
super.onResume();
getActivity().getActionBar().setSubtitle(listbook_last_subtitle);
}
...

public abstract class RootFragment extends Fragment implements OnBackPressListener {
#Override
public boolean onBackPressed() {
return new BackPressImpl(this).onBackPressed();
}
public abstract void OnRefreshUI();
}
public class BackPressImpl implements OnBackPressListener {
private Fragment parentFragment;
public BackPressImpl(Fragment parentFragment) {
this.parentFragment = parentFragment;
}
#Override
public boolean onBackPressed() {
((RootFragment) parentFragment).OnRefreshUI();
}
}
and final extent your Frament from RootFragment to see effect

My workaround is to get the current title of the actionbar in the Fragment before setting it to the new title. This way, once the Fragment is popped, I can change back to that title.
#Override
public void onResume() {
super.onResume();
// Get/Backup current title
mTitle = ((ActionBarActivity) getActivity()).getSupportActionBar()
.getTitle();
// Set new title
((ActionBarActivity) getActivity()).getSupportActionBar()
.setTitle(R.string.this_fragment_title);
}
#Override
public void onDestroy() {
// Set title back
((ActionBarActivity) getActivity()).getSupportActionBar()
.setTitle(mTitle);
super.onDestroy();
}

I have used enum FragmentTags to define all my fragment classes.
TAG_FOR_FRAGMENT_A(A.class),
TAG_FOR_FRAGMENT_B(B.class),
TAG_FOR_FRAGMENT_C(C.class)
pass FragmentTags.TAG_FOR_FRAGMENT_A.name() as fragment tag.
and now on
#Override
public void onBackPressed(){
FragmentManager fragmentManager = getFragmentManager();
Fragment current
= fragmentManager.findFragmentById(R.id.fragment_container);
FragmentTags fragmentTag = FragmentTags.valueOf(current.getTag());
switch(fragmentTag){
case TAG_FOR_FRAGMENT_A:
finish();
break;
case TAG_FOR_FRAGMENT_B:
fragmentManager.popBackStack();
break;
case default:
break;
}

Related

"Can not perform this action after onSaveInstanceState" when fragment is replaced

In my project context, I have a Button b in a Fragment f(1) in an Activity a.
Fragment f(x) is an instance of F where content depends of argument x
I need to replace the current instance f(1) by an instance f(2) on b click event:
From Activity a:
private void setFragment(int x) {
Bundle args = new Bundle();
args.putInt("x", x);
F f = new F();
f.setArguments(args);
f.setListener(new F.Listener() {
public void onButtonClick(int x) {
setFragment(x);
}
});
getSupportFragmentManager()
.beginTransaction()
.replace(ID, f)
.commit();
}
From Fragment f:
b.setOnClickListener(new View.onClickListener() {
public void onClick(View view) {
listener.onButtonClick(x + 1);
}
});
My problem is:
An Exception is throw on b click event only if a configuration state change occurs:
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
Please, what is my error? I read many posts on this Exception but I don't found any solution
Edit: I just make a test without AsyncTask, see the code:
Try to rotate the screen and push the button
public class MainActivity extends ActionBarActivity {
#Override
protected void onCreate(Bundle state) {
super.onCreate(state);
setContentView(R.layout.activity_main);
if (state == null) {
setFragment(1);
}
}
private void setFragment(int id) {
Bundle args = new Bundle();
args.putInt("id", id);
MyFragment myFragment = new MyFragment();
myFragment.setArguments(args);
myFragment.setListener(new MyFragment.Listener() {
#Override
public void onClick(int id) {
setFragment(id);
}
});
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fragment, myFragment)
.commit();
}
public static class MyFragment extends Fragment{
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup view, Bundle state) {
return new Button(getActivity()) {
{
setText(String.valueOf(getArguments().getInt("id")));
setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
listener.onClick(getArguments().getInt("id") + 1);
}
});
}
};
}
private static interface Listener {
public void onClick(int id);
}
private Listener listener;
public void setListener(Listener listener) {
this.listener = listener;
}
}
}
The problem is the way you are setting the listener.
You are setting the listener, then you rotate your device from landscape to portrait. What happens after the rotation is:
Android create a brand new instance of MainActivity.
FragmentManager create a new instance of MyFragment internally and re-adds it automatically to the activity as it was before orientation change.
If you click on the button, the listener will be called. However, the listener is the listener of the previous activity (before rotation) which has been destroyed.
So not only you have a Memory Leak (the old activity can not be garbage collected, because it's referenced from here) but you also get this error.
How to do it correctly:
Well, the problem is NOT only setRetainInstanceState() you have not understood the Android Fragments lifecycle correctly. As mentioned above, Fragments are controlled by the FragmentManager (FragmentTransaction). So, yes everytime you rotate your screen a new Fragment instance will be created, but FragmentManager will attach them automatically for you (it's a little bit strange, but thats how Fragment works)
I would recommend to use an EventBus. The fragment will fire an Event onClick() and the activity will receive this event since it's subscribed. I recomment GreenDao EventBus.
Otherwise you can have a look at the official docs, but from my point of view they are teaching not a good solution, because your fragment and activity are hardly connected (not modular). They say you should use onAttach() like you can see in the sample from the documentation: http://developer.android.com/guide/components/fragments.html#EventCallbacks
Btw. a similar problem can occur if you are not using Fragment arguments for "passing" data. For more details read this blog: http://hannesdorfmann.com/android/fragmentargs/

Onbackpressed in fragment not working while implementing interface

I am trying to override onbackpressed method inside fragment . But it gives me syntax error that it should override a super method in interface. Why so? I have other methods like ondestroy also in my fragment class but no error. Why for this backpressed alone. I tried onkeydown also. Same error. Pasting below my code.
public TestClass extends Fragment implements Testinterface
{ #Override //error must override or implement supertype method
public void onBackPressed ()
{
if (check)
Do somethin
else
getActivity().finish ()
//super.onBackPressed () // error here if I use this
}
You have to implement on key down in fragment, check for key code. The onBackPress() method can be used in an Activity -- which is the logical parent of your fragment.
Try this:
frag.getView().setFocusableInTouchMode(true);
frag.getView().setOnKeyListener( new OnKeyListener(){
#Override
public boolean onKey( View v, int keyCode, KeyEvent event ){
if( keyCode == KeyEvent.KEYCODE_BACK ){
return true;
}
return false;
}
} );
You can propagate onBackPressed() to all your fragments,for that you need to create two classes with the following methods and then make all your activties and fragments inherit from them:
public class BaseActivity extends ActionBarActivity {
#Override
public void onBackPressed() {
boolean eventConsumed = false;
List<Fragment> fragments = getSupportFragmentManager().getFragments();
if (fragments != null) {
for (Fragment fragment : fragments) {
if (fragment instanceof BaseFragment) {
eventConsumed = eventConsumed
|| ((BaseFragment) fragment).onBackPressed();
}
}
}
if (!eventConsumed) {
super.onBackPressed();
}
}
}
public class BaseFragment extends Fragment {
public boolean onBackPressed() {
return false;
}
}
Note that this code is using the support library, if you are not using it you need to do the appropriate changes.
Try to avoid using onKey in fragment. There is a better way.
For maintenance, I recommend you to use getBackStackEntryCount()
in Activity
final FragmentMananger fm = new getSupportFragmentManager();
#Override
protected void onCreate(Bundle b){
super.onCreate(b);
setContentView(R.layout.activity_main);
Fragment fragment = new SomeFragment();
// if you don't run on prior to Android 3.0 use getFragmentManager();
fm.beginTransaction().replace(R.id.frame_container, fragment).addToBackStack(null).commit();
// R.id.frame_container is the id of FrameLayout in activity_main.xml
}
#Override
public void onBackPressed(){
if(fm.getBackStackEntryCount() == 0){
finish();
}else{
super.onBackPressed();
}
}

Switching Fragments in Master/Detail Flow

I am attempting to create an app which has a Master/Detail flow using Fragments. Selecting an item will open a detail fragment which may then which to "open" another fragment and add it to the back stack.
I have renamed classes to help illustrate what they do.
public class ListOfDetails extends FragmentActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
}
//Callback method indicating that an item with the given ID was selected.
public void onItemSelected(String id) {
// Performing logic to determine what fragment to start omitted
if (ifTwoPanes()) {
Fragment fragment = new DetailFragmentType1();
getSupportFragmentManager().beginTransaction().replace(R.id.aContainer, fragment).commit();
} else {
Intent newIntent = new Intent(this, SinglePaneFragmentWrapper.class);
newIntent.putExtra("id", id);
startActivity(newIntent);
}
}
// My attempt at making it possible to change displayed fragment from within fragments
public void changeDetailFragment(Fragment fragment) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
transaction.addToBackStack(null);
transaction.replace(R.id.aContainer, fragment);
transaction.commit();
}
}
An example of one of the detail fragments. There are many different Fragments that may be created in different circumstances.
public class DetailFragmentType1 extends Fragment {
private ListOfDetails parent;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Activity a = getActivity();
if (a instanceof ListOfDetails) {
parent = (ListOfDetails) a;
}
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Button aButton = (Button) getActivity().findViewById(R.id.aButton);
aButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
parent.changeDetailFragment(new SubDetailFragment());
}
});
}
}
When on phone, a wrapper activity is used to hold the fragment
public class SinglePaneFragmentWrapper extends FragmentActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Duplicate logic must be performed to start fragment
// Performing logic to determine what fragment to start omitted
String id = getIntent().getStringExtra("id");
if(id == "DetailFragmentType1") {
Fragment fragment = new DetailFragmentType1();
getSupportFragmentManager().beginTransaction().replace(R.id.aContainer, fragment).commit();
} else {
...
}
}
}
What is the proper way to change the fragment that is open in the detail pane in this circumstance? My method feels like a hack when using two panes and doesn't even work when using only one pane because getParent() from SinglePaneFragmentWrapper returns null, making me unable to call parent.changeDetailFragment().
This is a complicated question, hopefully I explained it well. Let me know if I missed something. Thanks
There are lots of opinions around this and lots of ways of doing it. I think in this case the problem is "who is responsible for changing the fragment?" on the surface it seems that a listener on the button is the obvious place, but then the fragment shouldn't know what it is hosted in (a symptom of that is getting an undesirable result like null from getParent()).
In your case I would suggest you implement a "listener" interface in the parent and "notify" from the fragment.. when the parent is notified, it changes the fragment. This way the fragment is not changing itself (so doesn't need to know how).. so.. for your case..
Add a new interface:
public interface FragmentChangeListener {
void onFragmentChangeRequested(Fragment newFragment);
}
Implement the interface in your ListOfDetails activity
public class ListOfDetails extends FragmentActivity implements FragmentChangeListener {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
}
//Callback method indicating that an item with the given ID was selected.
public void onItemSelected(String id) {
// Performing logic to determine what fragment to start omitted
if (ifTwoPanes()) {
Fragment fragment = new DetailFragmentType1();
getSupportFragmentManager().beginTransaction().replace(R.id.aContainer, fragment).commit();
} else {
Intent newIntent = new Intent(this, SinglePaneFragmentWrapper.class);
newIntent.putExtra("id", id);
startActivity(newIntent);
}
}
// My attempt at making it possible to change displayed fragment from within fragments
public void changeDetailFragment(Fragment fragment) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
transaction.addToBackStack(null);
transaction.replace(R.id.aContainer, fragment);
transaction.commit();
}
// This is the interface implementation that will be called by your fragments
void onFragmentChangeRequested(Fragment newFragment) {
changeDetailFragment(newFragment);
}
}
Added listener to detail fragment
public class DetailFragmentType1 extends Fragment {
private FragmentChangeListener fragmentChangeListener;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Actually you might not have an activity here.. you should probably be
// doing this in onAttach
//Activity a = getActivity();
//if (a instanceof ListOfDetails) {
// parent = (ListOfDetails) a;
//}
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Button aButton = (Button) getActivity().findViewById(R.id.aButton);
aButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
// parent.changeDetailFragment(new SubDetailFragment());
notifyFragmentChange(new SubDetailFragment());
}
});
}
#Override
public void onAttach(Activity activity) {
// This is called when the fragment is attached to an activity..
if (activity instanceof FragmentChangeListener) {
fragmentChangeListener = (FragmentChangeListener) activity;
} else {
// Find your bugs early by making them clear when you can...
if (BuildConfig.DEBUG) {
throw new IllegalArgumentException("Fragment hosts must implement FragmentChangeListener");
}
}
}
private void notifyFragmentChange(Fragment newFragment) {
FragmentChangeListener listener = fragmentChangeListener;
if (listener != null) {
listener.onFragmentChangeRequested(newFragment);
}
}
}
And implement the same interface to your single pane activity...
public class SinglePaneFragmentWrapper extends FragmentActivity implements FragmentChangeListener {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Duplicate logic must be performed to start fragment
// Performing logic to determine what fragment to start omitted
String id = getIntent().getStringExtra("id");
if(id == "DetailFragmentType1") {
Fragment fragment = new DetailFragmentType1();
getSupportFragmentManager().beginTransaction().replace(R.id.aContainer, fragment).commit();
} else {
...
}
}
// My attempt at making it possible to change displayed fragment from within fragments
public void changeDetailFragment(Fragment fragment) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
transaction.addToBackStack(null);
transaction.replace(R.id.aContainer, fragment);
transaction.commit();
}
// This is the interface implementation that will be called by your fragments
void onFragmentChangeRequested(Fragment newFragment) {
changeDetailFragment(newFragment);
}
}
Note the similarity between your single pane and your multi-pane activities.. this suggests that you could either put all of the duplicated code (changefragment etc) into a single activity that they both extend or that in maybe they are the same activities with different layouts...
I hope that helps, Good luck.
Regards,
CJ

Replacing a Fragment/Tab in a Viewpager

I am currently trying to make framework for my future apps. I really like the ActionBar and the ViewPager, but miss a feature. I need to replace a Fragment/Tab in runtime.
Using the offical example, I would love to see something like a replaceTab(), but the Fragment itself isn't updated, no matter what I do.
You have to use a FragmentPagerAdapter to handle your fragment changes. There are 3 important things:
remove the previous fragment if it exists.
call notifyDataSetChanged() to refresh the list of pages.
return POSITION_NONE in getItemPosition if it's asking for an old fragment.
Here is the code I use with a Left and a Right page. I change dynamically the fragments depending on what the user does.
public class MyPager extends ViewPager {
private MyPagerAdapter mMyPagerAdapter;
public MyPager(Context context, FragmentActivity activity) {
super(context);
mMyPagerAdapter = new MyPagerAdapter(activity.getSupportFragmentManager());
setAdapter(mMyPagerAdapter);
}
public void replaceLeftFragment(Fragment fragment) {
mMyPagerAdapter.replaceLeftFragment(fragment);
}
public void replaceRightFragment(Fragment fragment) {
mMyPagerAdapter.replaceRightFragment(fragment);
}
}
public class MyPagerAdapter extends FragmentPagerAdapter {
private FragmentManager mFragmentManager;
private Fragment mLeftFragment;
private Fragment mRightFragment;
public MyPagerAdapter(FragmentManager fm) {
super(fm);
mFragmentManager = fm;
}
#Override
public Fragment getItem(int position) {
if (position == 0) {
return mLeftFragment;
} else {
return mRightFragment;
}
}
#Override
public int getCount() {
final int nbLeft = (mLeftFragment == null) ? 0 : 1;
final int nbRight = (mRightFragment == null) ? 0 : 1;
return (nbLeft + nbRight);
}
public void replaceLeftFragment(Fragment fragment) {
if (mLeftFragment != null) {
mFragmentManager.beginTransaction().remove(mLeftFragment).commit();
}
mLeftFragment = fragment;
notifyDataSetChanged();
}
public void replaceRightFragment(Fragment fragment) {
if (mRightFragment != null) {
mFragmentManager.beginTransaction().remove(mRightFragment).commit();
}
mRightFragment = fragment;
notifyDataSetChanged();
}
#Override
public int getItemPosition(Object object) {
if ((object!=mLeftFragment) && (object!=mRightFragment)) {
return POSITION_NONE;
}
return super.getItemPosition(object);
}
}
Do you try:
fragmentTransaction.remove(yourframent);
and then:
fragmentTransaction.add(yournewfragment);
If your Fragment is created from a layout file, you cannot replace it at runtime. You can only successfully replace Fragments you added programmatically. See Android: can't replace one fragment with another
Very close to what you want, I needed to add fragment (at the end) to a ViewPager, and did this:
FragmentTransaction ft = fragmentManager.beginTransaction();
new_fragment = Fragment.instantiate(activity, class_name, args);
// The below code removes old fragment and adds the new one at the end.
ft.attach(new_fragment); // <- this adds the fragment at the end
ft.detach(old_fragment); // <- this removes old fragment
// This should be what you're looking for when adding to an Activity instead of a ViewPager
ft.replace(view_id, new_fragment); // <- this is supposed to replace the fragment attached to view_id
// Or if the replace does not work, this might work:
ft.detach(old_fragment);
ft.add(view_id, new_fragment);
// Not to be forgotten: ;)
ft.commit();
The above code might need some adjustments, maybe only require the replace call to effectively work?
If the replace call fails, you can still detach all fragments and re-attach them in the order desired.

support FragmentPagerAdapter holds reference to old fragments

I have narrowed my problem down to being a problem with the FragmentManager retaining instances of old fragments and my viewpager being out of sync with my FragmentManager. See this issue: http://code.google.com/p/android/issues/detail?id=19211#makechanges. I still have no clue how to solve this. Any suggestions?
I have tried to debug this for a long time and any help would be greatly appreciated. I am using a FragmentPagerAdapter which accepts a list of fragments like so:
List<Fragment> fragments = new Vector<Fragment>();
fragments.add(Fragment.instantiate(this, Fragment1.class.getName()));
...
new PagerAdapter(getSupportFragmentManager(), fragments);
The implementation is standard. I am using ActionBarSherlock and v4 computability library for Fragments.
My problem is that after leaving the app and opening several other applications and coming back, the fragments lose their reference back to the FragmentActivity (ie. getActivity() == null). I can not figure out why this is happening. I tried to manually set setRetainInstance(true); but this does not help. I figured that this happens when my FragmentActivity gets destroyed, however this still happens if I open the app before I get the log message. Are there any ideas?
#Override
protected void onDestroy(){
Log.w(TAG, "DESTROYDESTROYDESTROYDESTROYDESTROYDESTROYDESTROY");
super.onDestroy();
}
The adapter:
public class PagerAdapter extends FragmentPagerAdapter {
private List<Fragment> fragments;
public PagerAdapter(FragmentManager fm, List<Fragment> fragments) {
super(fm);
this.fragments = fragments;
}
#Override
public Fragment getItem(int position) {
return this.fragments.get(position);
}
#Override
public int getCount() {
return this.fragments.size();
}
}
One of my Fragments stripped but I commented everything out that's stripped and it still doesn't work.
public class MyFragment extends Fragment implements MyFragmentInterface, OnScrollListener {
...
#Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
handler = new Handler();
setHasOptionsMenu(true);
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
Log.w(TAG,"ATTACHATTACHATTACHATTACHATTACH");
context = activity;
if(context== null){
Log.e("IS NULL", "NULLNULLNULLNULLNULLNULLNULLNULLNULLNULLNULL");
}else{
Log.d("IS NOT NULL", "NOTNOTNOTNOTNOTNOTNOTNOT");
}
}
#Override
public void onActivityCreated(Bundle savedState) {
super.onActivityCreated(savedState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.my_fragment,container, false);
return v;
}
#Override
public void onResume(){
super.onResume();
}
private void callService(){
// do not call another service is already running
if(startLoad || !canSet) return;
// set flag
startLoad = true;
canSet = false;
// show the bottom spinner
addFooter();
Intent intent = new Intent(context, MyService.class);
intent.putExtra(MyService.STATUS_RECEIVER, resultReceiver);
context.startService(intent);
}
private ResultReceiver resultReceiver = new ResultReceiver(null) {
#Override
protected void onReceiveResult(int resultCode, final Bundle resultData) {
boolean isSet = false;
if(resultData!=null)
if(resultData.containsKey(MyService.STATUS_FINISHED_GET)){
if(resultData.getBoolean(MyService.STATUS_FINISHED_GET)){
removeFooter();
startLoad = false;
isSet = true;
}
}
switch(resultCode){
case MyService.STATUS_FINISHED:
stopSpinning();
break;
case SyncService.STATUS_RUNNING:
break;
case SyncService.STATUS_ERROR:
break;
}
}
};
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
menu.clear();
inflater.inflate(R.menu.activity, menu);
}
#Override
public void onPause(){
super.onPause();
}
public void onScroll(AbsListView arg0, int firstVisible, int visibleCount, int totalCount) {
boolean loadMore = /* maybe add a padding */
firstVisible + visibleCount >= totalCount;
boolean away = firstVisible+ visibleCount <= totalCount - visibleCount;
if(away){
// startLoad can now be set again
canSet = true;
}
if(loadMore)
}
public void onScrollStateChanged(AbsListView arg0, int state) {
switch(state){
case OnScrollListener.SCROLL_STATE_FLING:
adapter.setLoad(false);
lastState = OnScrollListener.SCROLL_STATE_FLING;
break;
case OnScrollListener.SCROLL_STATE_IDLE:
adapter.setLoad(true);
if(lastState == SCROLL_STATE_FLING){
// load the images on screen
}
lastState = OnScrollListener.SCROLL_STATE_IDLE;
break;
case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
adapter.setLoad(true);
if(lastState == SCROLL_STATE_FLING){
// load the images on screen
}
lastState = OnScrollListener.SCROLL_STATE_TOUCH_SCROLL;
break;
}
}
#Override
public void onDetach(){
super.onDetach();
if(this.adapter!=null)
this.adapter.clearContext();
Log.w(TAG, "DETACHEDDETACHEDDETACHEDDETACHEDDETACHEDDETACHED");
}
public void update(final int id, String name) {
if(name!=null){
getActivity().getSupportActionBar().setTitle(name);
}
}
}
The update method is called when a user interacts with a different fragment and the getActivity is returning null. Here is the method the other fragment is calling:
((MyFragment) pagerAdapter.getItem(1)).update(id, name);
I believe that when the app is destroyed then created again instead of just starting the app up to the default fragment the app starts up and then viewpager navigates to the last known page. This seems strange, shouldn't the app just load to the default fragment?
You are running into a problem because you are instantiating and keeping references to your fragments outside of PagerAdapter.getItem, and are trying to use those references independently of the ViewPager. As Seraph says, you do have guarantees that a fragment has been instantiated/added in a ViewPager at a particular time - this should be considered an implementation detail. A ViewPager does lazy loading of its pages; by default it only loads the current page, and the one to the left and right.
If you put your app into the background, the fragments that have been added to the fragment manager are saved automatically. Even if your app is killed, this information is restored when you relaunch your app.
Now consider that you have viewed a few pages, Fragments A, B and C. You know that these have been added to the fragment manager. Because you are using FragmentPagerAdapter and not FragmentStatePagerAdapter, these fragments will still be added (but potentially detached) when you scroll to other pages.
Consider that you then background your application, and then it gets killed. When you come back, Android will remember that you used to have Fragments A, B and C in the fragment manager and so it recreates them for you and then adds them. However, the ones that are added to the fragment manager now are NOT the ones you have in your fragments list in your Activity.
The FragmentPagerAdapter will not try to call getPosition if there is already a fragment added for that particular page position. In fact, since the fragment recreated by Android will never be removed, you have no hope of replacing it with a call to getPosition. Getting a handle on it is also pretty difficult to obtain a reference to it because it was added with a tag that is unknown to you. This is by design; you are discouraged from messing with the fragments that the view pager is managing. You should be performing all your actions within a fragment, communicating with the activity, and requesting to switch to a particular page, if necessary.
Now, back to your problem with the missing activity. Calling pagerAdapter.getItem(1)).update(id, name) after all of this has happened returns you the fragment in your list, which has yet to be added to the fragment manager, and so it will not have an Activity reference. I would that suggest your update method should modify some shared data structure (possibly managed by the activity), and then when you move to a particular page it can draw itself based on this updated data.
I found simple solution which worked for me.
Make your fragment adapter extends FragmentStatePagerAdapter instead of FragmentPagerAdapter and override method onSave to return null
#Override
public Parcelable saveState()
{
return null;
}
This prevent android from recreating fragment
One day later I found another and better solution.
Call setRetainInstance(true) for all your fragments and save references to them somewhere. I did that in static variable in my activity, because it's declared as singleTask and fragments can stay the same all the time.
This way android not recreate fragments but use same instances.
I solved this issue by accessing my fragments directly through the FragmentManager instead of via the FragmentPagerAdapter like so. First I need to figure out the tag of the fragment auto generated by the FragmentPagerAdapter...
private String getFragmentTag(int pos){
return "android:switcher:"+R.id.viewpager+":"+pos;
}
Then I simply get a reference to that fragment and do what I need like so...
Fragment f = this.getSupportFragmentManager().findFragmentByTag(getFragmentTag(1));
((MyFragmentInterface) f).update(id, name);
viewPager.setCurrentItem(1, true);
Inside my fragments I set the setRetainInstance(false); so that I can manually add values to the savedInstanceState bundle.
#Override
public void onSaveInstanceState(Bundle outState) {
if(this.my !=null)
outState.putInt("myId", this.my.getId());
super.onSaveInstanceState(outState);
}
and then in the OnCreate i grab that key and restore the state of the fragment as necessary. An easy solution which was hard (for me at least) to figure out.
Global working tested solution.
getSupportFragmentManager() keeps the null reference some times and View pager does not create new since it find reference to same fragment. So to over come this use getChildFragmentManager() solves problem in simple way.
Don't do this:
new PagerAdapter(getSupportFragmentManager(), fragments);
Do this:
new PagerAdapter(getChildFragmentManager() , fragments);
Do not try to interact between fragments in ViewPager. You cannot guarantee that other fragment attached or even exists. Istead of changing actionbar title from fragment, you can do it from your activity. Use standart interface pattern for this:
public interface UpdateCallback
{
void update(String name);
}
public class MyActivity extends FragmentActivity implements UpdateCallback
{
#Override
public void update(String name)
{
getSupportActionBar().setTitle(name);
}
}
public class MyFragment extends Fragment
{
private UpdateCallback callback;
#Override
public void onAttach(SupportActivity activity)
{
super.onAttach(activity);
callback = (UpdateCallback) activity;
}
#Override
public void onDetach()
{
super.onDetach();
callback = null;
}
public void updateActionbar(String name)
{
if(callback != null)
callback.update(name);
}
}
You can remove the fragments when destroy the viewpager, in my case, I removed them on onDestroyView() of my fragment:
#Override
public void onDestroyView() {
if (getChildFragmentManager().getFragments() != null) {
for (Fragment fragment : getChildFragmentManager().getFragments()) {
getChildFragmentManager().beginTransaction().remove(fragment).commitAllowingStateLoss();
}
}
super.onDestroyView();
}
After a few hours of looking for a similar issue I think a have another solution. This one at least it worked for me and I only have to changed a couple of lines.
This is the problem I had, I have an activity with a view pager that uses a FragmentStatePagerAdapter with two Fragments. Everything works fine until I force the activity to get destroyed (developer options) or I rotate the screen. I do keep a reference to the two fragments after they get created inside the method getItem.
At that point the activity will be created again and everything works fine at this point but I have lost the reference to my fragmetns as getItem doesn't' get called again.
This is how I fixed that problem, inside the FragmentStatePagerAdapter:
#Override
public Object instantiateItem(ViewGroup container, int position) {
Object aux = super.instantiateItem(container, position);
//Update the references to the Fragments we have on the view pager
if(position==0){
fragTabOne = (FragOffersList)aux;
}
else{
fragTabTwo = (FragOffersList) aux;
}
return aux;
}
You won't get a call on getItem again if the adapter already has a reference to it internally, and you shouldn't change that. Instead you can get the fragment it's being used by looking at this other method instantiateItem() which will be called for each of your fragments.
Hope that helps anyone.
Since people don't tend to read comments, here is an answer that mostly duplicates what I wrote here:
the root cause of the issue is the fact that android system does not call getItem to obtain fragments that are actually displayed, but instantiateItem. This method first tries to lookup and reuse a fragment instance for a given tab in FragmentManager. Only if this lookup fails (which happens only the first time when FragmentManager is newly created) then getItem is called. It is for obvious reasons not to recreate fragments (that may be heavy) for example each time a user rotates his device.
To solve this, instead of creating fragments with Fragment.instantiate in your activity, you should do it with pagerAdapter.instantiateItem and all these calls should be surrounded by startUpdate/finishUpdate method calls that start/commit fragment transaction respectively. getItem should be the place where fragments are really created using their respective constructors.
List<Fragment> fragments = new Vector<Fragment>();
#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);
fragments.add(adapter.instantiateItem(viewPager, 0));
fragments.add(adapter.instantiateItem(viewPager, 1));
// and so on if you have more tabs...
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
}
}
Since the FragmentManager will take care of restoring your Fragments for you as soon as the onResume() method is called I have the fragment call out to the activity and add itself to a list. In my instance I am storing all of this in my PagerAdapter implementation. Each fragment knows it's position because it is added to the fragment arguments on creation. Now whenever I need to manipulate a fragment at a specific index all I have to do is use the list from my adapter.
The following is an example of an Adapter for a custom ViewPager that will grow the fragment as it moves into focus, and scale it down as it moves out of focus. Besides the Adapter and Fragment classes I have here all you need is for the parent activity to be able to reference the adapter variable and you are set.
Adapter
public class GrowPagerAdapter extends FragmentPagerAdapter implements OnPageChangeListener, OnScrollChangedListener {
public final String TAG = this.getClass().getSimpleName();
private final int COUNT = 4;
public static final float BASE_SIZE = 0.8f;
public static final float BASE_ALPHA = 0.8f;
private int mCurrentPage = 0;
private boolean mScrollingLeft;
private List<SummaryTabletFragment> mFragments;
public int getCurrentPage() {
return mCurrentPage;
}
public void addFragment(SummaryTabletFragment fragment) {
mFragments.add(fragment.getPosition(), fragment);
}
public GrowPagerAdapter(FragmentManager fm) {
super(fm);
mFragments = new ArrayList<SummaryTabletFragment>();
}
#Override
public int getCount() {
return COUNT;
}
#Override
public Fragment getItem(int position) {
return SummaryTabletFragment.newInstance(position);
}
#Override
public void onPageScrollStateChanged(int state) {}
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
adjustSize(position, positionOffset);
}
#Override
public void onPageSelected(int position) {
mCurrentPage = position;
}
/**
* Used to adjust the size of each view in the viewpager as the user
* scrolls. This provides the effect of children scaling down as they
* are moved out and back to full size as they come into focus.
*
* #param position
* #param percent
*/
private void adjustSize(int position, float percent) {
position += (mScrollingLeft ? 1 : 0);
int secondary = position + (mScrollingLeft ? -1 : 1);
int tertiary = position + (mScrollingLeft ? 1 : -1);
float scaleUp = mScrollingLeft ? percent : 1.0f - percent;
float scaleDown = mScrollingLeft ? 1.0f - percent : percent;
float percentOut = scaleUp > BASE_ALPHA ? BASE_ALPHA : scaleUp;
float percentIn = scaleDown > BASE_ALPHA ? BASE_ALPHA : scaleDown;
if (scaleUp < BASE_SIZE)
scaleUp = BASE_SIZE;
if (scaleDown < BASE_SIZE)
scaleDown = BASE_SIZE;
// Adjust the fragments that are, or will be, on screen
SummaryTabletFragment current = (position < mFragments.size()) ? mFragments.get(position) : null;
SummaryTabletFragment next = (secondary < mFragments.size() && secondary > -1) ? mFragments.get(secondary) : null;
SummaryTabletFragment afterNext = (tertiary < mFragments.size() && tertiary > -1) ? mFragments.get(tertiary) : null;
if (current != null && next != null) {
// Apply the adjustments to each fragment
current.transitionFragment(percentIn, scaleUp);
next.transitionFragment(percentOut, scaleDown);
if (afterNext != null) {
afterNext.transitionFragment(BASE_ALPHA, BASE_SIZE);
}
}
}
#Override
public void onScrollChanged(int l, int t, int oldl, int oldt) {
// Keep track of which direction we are scrolling
mScrollingLeft = (oldl - l) < 0;
}
}
Fragment
public class SummaryTabletFragment extends BaseTabletFragment {
public final String TAG = this.getClass().getSimpleName();
private final float SCALE_SIZE = 0.8f;
private RelativeLayout mBackground, mCover;
private TextView mTitle;
private VerticalTextView mLeft, mRight;
private String mTitleText;
private Integer mColor;
private boolean mInit = false;
private Float mScale, mPercent;
private GrowPagerAdapter mAdapter;
private int mCurrentPosition = 0;
public String getTitleText() {
return mTitleText;
}
public void setTitleText(String titleText) {
this.mTitleText = titleText;
}
public static SummaryTabletFragment newInstance(int position) {
SummaryTabletFragment fragment = new SummaryTabletFragment();
fragment.setRetainInstance(true);
Bundle args = new Bundle();
args.putInt("position", position);
fragment.setArguments(args);
return fragment;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
mRoot = inflater.inflate(R.layout.tablet_dummy_view, null);
setupViews();
configureView();
return mRoot;
}
#Override
public void onViewStateRestored(Bundle savedInstanceState) {
super.onViewStateRestored(savedInstanceState);
if (savedInstanceState != null) {
mColor = savedInstanceState.getInt("color", Color.BLACK);
}
configureView();
}
#Override
public void onSaveInstanceState(Bundle outState) {
outState.putInt("color", mColor);
super.onSaveInstanceState(outState);
}
#Override
public int getPosition() {
return getArguments().getInt("position", -1);
}
#Override
public void setPosition(int position) {
getArguments().putInt("position", position);
}
public void onResume() {
super.onResume();
mAdapter = mActivity.getPagerAdapter();
mAdapter.addFragment(this);
mCurrentPosition = mAdapter.getCurrentPage();
if ((getPosition() == (mCurrentPosition + 1) || getPosition() == (mCurrentPosition - 1)) && !mInit) {
mInit = true;
transitionFragment(GrowPagerAdapter.BASE_ALPHA, GrowPagerAdapter.BASE_SIZE);
return;
}
if (getPosition() == mCurrentPosition && !mInit) {
mInit = true;
transitionFragment(0.00f, 1.0f);
}
}
private void setupViews() {
mCover = (RelativeLayout) mRoot.findViewById(R.id.cover);
mLeft = (VerticalTextView) mRoot.findViewById(R.id.title_left);
mRight = (VerticalTextView) mRoot.findViewById(R.id.title_right);
mBackground = (RelativeLayout) mRoot.findViewById(R.id.root);
mTitle = (TextView) mRoot.findViewById(R.id.title);
}
private void configureView() {
Fonts.applyPrimaryBoldFont(mLeft, 15);
Fonts.applyPrimaryBoldFont(mRight, 15);
float[] size = UiUtils.getScreenMeasurements(mActivity);
int width = (int) (size[0] * SCALE_SIZE);
int height = (int) (size[1] * SCALE_SIZE);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(width, height);
mBackground.setLayoutParams(params);
if (mScale != null)
transitionFragment(mPercent, mScale);
setRandomBackground();
setTitleText("Fragment " + getPosition());
mTitle.setText(getTitleText().toUpperCase());
mLeft.setText(getTitleText().toUpperCase());
mRight.setText(getTitleText().toUpperCase());
mLeft.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
mActivity.showNextPage();
}
});
mRight.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
mActivity.showPrevPage();
}
});
}
private void setRandomBackground() {
if (mColor == null) {
Random r = new Random();
mColor = Color.rgb(r.nextInt(255), r.nextInt(255), r.nextInt(255));
}
mBackground.setBackgroundColor(mColor);
}
public void transitionFragment(float percent, float scale) {
this.mScale = scale;
this.mPercent = percent;
if (getView() != null && mCover != null) {
getView().setScaleX(scale);
getView().setScaleY(scale);
mCover.setAlpha(percent);
mCover.setVisibility((percent <= 0.05f) ? View.GONE : View.VISIBLE);
}
}
#Override
public String getFragmentTitle() {
return null;
}
}
My solution: I set almost every View as static. Now my app interacts perfect. Being able to call the static methods from everywhere is maybe not a good style, but why to play around with code that doesn't work? I read a lot of questions and their answers here on SO and no solution brought success (for me).
I know it can leak the memory, and waste heap, and my code will not be fit on other projects, but I don't feel scared about this - I tested the app on different devices and conditions, no problems at all, the Android Platform seems to be able handle this. The UI gets refreshed every second and even on a S2 ICS (4.0.3) device the app is able to handle thousands of geo-markers.
I faced the same issue but my ViewPager was inside a TopFragment which created and set an adapter using setAdapter(new FragmentPagerAdapter(getChildFragmentManager())).
I fixed this issue by overriding onAttachFragment(Fragment childFragment) in the TopFragment like this:
#Override
public void onAttachFragment(Fragment childFragment) {
if (childFragment instanceof OnboardingDiamondsFragment) {
mChildFragment = (ChildFragment) childFragment;
}
super.onAttachFragment(childFragment);
}
As known already (see answers above), when the childFragmentManager recreate itself, it also create the fragments which were inside the viewPager.
The important part is that after that, he calls onAttachFragment and now we have a reference to the new recreated fragment!
Hope this will help anyone getting this old Q like me :)
I solved the problem by saving the fragments in SparceArray:
public abstract class SaveFragmentsPagerAdapter extends FragmentPagerAdapter {
SparseArray<Fragment> fragments = new SparseArray<>();
public SaveFragmentsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Object instantiateItem(ViewGroup container, int position) {
Fragment fragment = (Fragment) super.instantiateItem(container, position);
fragments.append(position, fragment);
return fragment;
}
#Nullable
public Fragment getFragmentByPosition(int position){
return fragments.get(position);
}
}
Just so you know...
Adding to the litany of woes with these classes, there is a rather interesting bug that's worth sharing.
I'm using a ViewPager to navigate a tree of items (select an item and the view pager animates scrolling to the right, and the next branch appears, navigate back, and the ViewPager scrolls in the opposite direction to return to the previous node).
The problem arises when I push and pop fragments off the end of the FragmentStatePagerAdapter. It's smart enough to notice that the items change, and smart enough to create and replace a fragment when the item has changed. But not smart enough to discard the fragment state, or smart enough to trim the internally saved fragment states when the adapter size changes. So when you pop an item, and push a new one onto the end, the fragment for the new item gets the saved state of the fragment for the old item, which caused absolute havoc in my code. My fragments carry data that may require a lot of work to refetch from the internet, so not saving state really wasn't an option.
I don't have a clean workaround. I used something like this:
public void onSaveInstanceState(Bundle outState) {
IFragmentListener listener = (IFragmentListener)getActivity();
if (listener!= null)
{
if (!listener.isStillInTheAdapter(this.getAdapterItem()))
{
return; // return empty state.
}
}
super.onSaveInstanceState(outState);
// normal saving of state for flips and
// paging out of the activity follows
....
}
An imperfect solution because the new fragment instance still gets a savedState Bundle, but at least it doesn't carry stale data.

Categories

Resources