android fragment- How to save states of views in a fragment when another fragment is pushed on top of it
Asked Answered
L

12

139

In android, a fragment (say FragA) gets added to the backstack and another fragment (say FragB) comes to the top. Now on hitting back FragA comes to the top and the onCreateView() is called. Now I had FragA in a particular state before FragB got pushed on top of it.

My Question is how can I restore FragA to its previous state ? Is there a way to save state (like say in a Bundle) and if so then which method should I override ?

Locate answered 22/7, 2011 at 7:45 Comment(0)
R
97

In fragment guide FragmentList example you can find:

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putInt("curChoice", mCurCheckPosition);
}

Which you can use later like this:

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    if (savedInstanceState != null) {
        // Restore last state for checked position.
        mCurCheckPosition = savedInstanceState.getInt("curChoice", 0);
    }
}

I'm a beginner in Fragments but it seems like solution of your problem ;) OnActivityCreated is invoked after fragment returns from back stack.

Rime answered 22/7, 2011 at 8:24 Comment(13)
I couldn't get this to work savedInstanceState was always null. I'm adding fragment via xml layout. Had to change the mCurCheckPosition to static then it works, but feels hacky.Ineffective
@Ineffective does your fragment have id (if not, it should).Rime
Yeah it does, I should of noted that in the comment, thanks anyway.Ineffective
it doesn't call onSaveInstanceState - why would it ? So, this approach doesn't work.Eurhythmics
@Eurhythmics if it doesn't you should look for reason why it is not called, eg #8504129Rime
Will this approach really work in case we want to retain the fragment state while returning from another fragment in the same Activity? onSaveInstanceState() gets called only on Activity onPause/onStop events. According to the documentations: "Also like an activity, you can retain the state of a fragment using a Bundle, in case the activity's process is killed and you need to restore the fragment state when the activity is recreated. You can save the state during the fragment's onSaveInstanceState() callback and restore it during either onCreate(), onCreateView(), or onActivityCreated()."Foy
when we use popbackstack() then onSaveInstanceState() will be called.The above method will workHost
For the record this approach is wrong and should have no where near the up-votes it has. onSaveInstanceState is only called when it's corresponding activity is also shutting down.Rosaliarosalie
onSaveInstanceState() is called onle when config changes occured and activity gets destroyed, this answer is wrongTalented
Does it will work if i replace a fragment from container with adding that transaction to backstack.?Coexecutor
tell me about mCurCheckPosition variable? its datatype?Sized
This approach works, because when when you return from another fragment within same activity the fragment members are still there, there is no need to restore them.Istanbul
This is totally broken in Android and has been for along time. I'd suggest making you're own caching system using serializable in the onStop() and onStart() methods of the fragment. This is clear and you don't have to deal with the s*** that is Android development.Cranky
B
87

Fragment's onSaveInstanceState(Bundle outState) will never be called unless fragment's activity call it on itself and attached fragments. Thus this method won't be called until something (typically rotation) force activity to SaveInstanceState and restore it later. But if you have only one activity and large set of fragments inside it (with intensive usage of replace) and application runs only in one orientation activity's onSaveInstanceState(Bundle outState) may not be called for a long time.

I know three possible workarounds.

The first:

use fragment's arguments to hold important data:

public class FragmentA extends Fragment {
    private static final String PERSISTENT_VARIABLE_BUNDLE_KEY = "persistentVariable";

    private EditText persistentVariableEdit;

    public FragmentA() {
        setArguments(new Bundle());
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_a, null);

        persistentVariableEdit = (EditText) view.findViewById(R.id.editText);

        TextView proofTextView = (TextView) view.findViewById(R.id.textView);

        Bundle mySavedInstanceState = getArguments();
        String persistentVariable = mySavedInstanceState.getString(PERSISTENT_VARIABLE_BUNDLE_KEY);

        proofTextView.setText(persistentVariable);


        view.findViewById(R.id.btnPushFragmentB).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                getFragmentManager()
                        .beginTransaction()
                        .replace(R.id.frameLayout, new FragmentB())
                        .addToBackStack(null)
                        .commit();
            }
        });

        return view;
    }

    @Override
    public void onPause() {
        super.onPause();
        String persistentVariable = persistentVariableEdit.getText().toString();

        getArguments().putString(PERSISTENT_VARIABLE_BUNDLE_KEY, persistentVariable);
    }
}

The second but less pedantic way - hold variables in singletons

The third - don't replace() fragments but add()/show()/hide() them instead.

Beall answered 31/7, 2014 at 20:37 Comment(8)
The best solution when Fragment.onSaveInstanceState() was never get called. Just save your own data to the argument, including the items in the list view or just their IDs (if you have other centralize data manager). No need to save list view's position - that was saved and restore automatically.Lucier
I tried to use your example in my app, but this: String persistentVariable = mySavedInstanceState.getString(PERSISTENT_VARIABLE_BUNDLE_KEY); is always null. What's the problem?Olodort
Use the fragment's getArguments() is DEFINITELY the way to go, including nested fragments in a ViewPager. I use 1 activity and swap many fragments in/out, and this works perfectly. Here's a simple test for you to vet any proposed solution: 1) go from Fragment A to Fragment B; 2) change device orientation twice; 3) press the back button on the device.Glendoraglendower
I tried the first approach but it did not work for me. getArguments() always returns null. It also make sense because the fragment is replaced and in onCreate() you set a new Bundle so the old Bundle is lost. What am I missing and were am I wrong?Antonyantonym
@Zvi, i wrote this code 1,5 years ago and don't remember all details, but as I remember, replace doesn't recreate fragments, fragment recreated only if you have created new instance from your code. In this case, obviously, constructor called and setArguments(new Bundle()); overwrites old Bundle. So make sure you created fragment only once, and then use this instance instead of creating new one every time.Beall
@Fyodor Volchyok, so really it is like the third option, but I am using new() in my application. I ended up using newInstance() passing the data from one fragment to the other and backAntonyantonym
Thanks for this answer.Transcribe
I think The Third way is the best. https://mcmap.net/q/168195/-fragmenttransaction-hide-show-doesn-39-t-work-sometimesHerefordshire
A
20

Just notice that if you work with Fragments using ViewPager, it's pretty easy. You only need to call this method: setOffscreenPageLimit().

Accordign to the docs:

Set the number of pages that should be retained to either side of the current page in the view hierarchy in an idle state. Pages beyond this limit will be recreated from the adapter when needed.

Similar issue here

Adon answered 6/4, 2013 at 22:15 Comment(4)
This is different. setOffScreenPageLimit acts like a cache (meaning how many pages the ViewPager has to handle at a given moment) but is not used to save the state of a Fragment.Strang
It my case it worked with setOffscreenPageLimit() - although the fragments were destroyed, the view state was saved and restored.Sylviasylviculture
Thanks, helped me too.Gardy
Years later and this is still so relevant. Although it doesn't really answer the question, it solves a problemShaw
F
20

Just inflate your View for once.

Exemple follows:

public class AFragment extends Fragment {

private View mRootView;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    if(mRootView==null){
        mRootView = inflater.inflate(R.id.fragment_a, container, false);
        //......
    }
    return mRootView;
}

}

Famous answered 11/10, 2016 at 6:48 Comment(5)
also should keep existing fragments in array or somethingIsocrates
I've heard that keeping a reference to a fragment's rootView is bad practice -- could result in leaks [citation needed]?Ortiz
@Ortiz Fragment refers to its root view will not do that. While reference by some GC-root elements will, like global static property, non-ui thread variable. A Fragment instance is not GC-root, so it can be garbage collected. So will its root view.Famous
You same my day.Felsite
Sweet and simple.Beauharnais
N
9

I worked with an issue very similar to this. Since I knew I would frequently be returning back to a previous fragment, I checked to see whether the fragment .isAdded() was true, and if so, rather than doing a transaction.replace() I just do a transaction.show(). This keeps the fragment from being recreated if it's already on the stack - no state saving needed.

Fragment target = <my fragment>;
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
if(target.isAdded()) {
    transaction.show(target);
} else {
    transaction.addToBackStack(button_id + "stack_item");
    transaction.replace(R.id.page_fragment, target);
}
transaction.commit();

Another thing to keep in mind is that while this preserves the natural order for fragments themselves, you might still need to handle the activity itself being destroyed and recreated on orientation (config) change. To get around this in AndroidManifest.xml for your node:

android:configChanges="orientation|screenSize"

In Android 3.0 and higher, the screenSize is apparently required.

Good luck

Nissa answered 26/10, 2012 at 14:29 Comment(6)
transaction.addToBackStack(button_id + "stack_item");//what does this line do.What is button_id here?Mispickel
button_id is just a made up variable. The string argument passed to addToBackStack is just an optional name for the backstack state - you can set it to null if you're only managing a single backstack.Nissa
,i am having an issue similar to this with fragments,can you please look into it-#22469477Mispickel
Don't ever add android:configChanges=everythinYouCanThinkOf|moreThingsYouFoundOnTheInternets in your manifest. Instead learn how to save and restore state, for example from here: speakerdeck.com/cyrilmottier/…Respiration
And then once you've learned how insanely unwieldy saving state is and that the solution presented solves the problem most cleanly in your particular situation, go ahead and use it, without concern for downvotes from those who disagree ;-)Nissa
android:configChanges="orientation|screenSize" this did it for meDelinda
G
5

if you are handling the config changes in your fragment activity specified in android manifest like this

<activity
    android:name=".courses.posts.EditPostActivity"
    android:configChanges="keyboardHidden|orientation"
    android:screenOrientation="unspecified" />

then the onSaveInstanceState of the fragment will not be invoked and the savedInstanceState object will always be null.

Governorship answered 10/7, 2012 at 17:13 Comment(0)
G
5

The Best Solution I found is below:

onSavedInstanceState(): always called inside fragment when activity is going to shut down(Move activity from one to another or config changes). So if we are calling multiple fragments on same activity then We have to use the following approach:

Use OnDestroyView() of the fragment and save the whole object inside that method. Then OnActivityCreated(): Check that if object is null or not(Because this method calls every time). Now restore state of an object here.

Its works always!

Gaskin answered 8/2, 2016 at 11:51 Comment(0)
M
1

i donot think onSaveInstanceState is a good solution. it just use for activity which had been destoryed.

From android 3.0 the Fragmen has been manager by FragmentManager, the condition is: one activity mapping manny fragments, when the fragment is added(not replace: it will recreated) in backStack, the view will be destored. when back to the last one, it will display as before.

So i think the fragmentManger and transaction is good enough to handle it.

Mitinger answered 20/4, 2016 at 3:43 Comment(0)
F
0

I used a hybrid approach for fragments containing a list view. It seems to be performant since I don't replace the current fragment but rather add the new fragment and hide the current one. I have the following method in the activity that hosts my fragments:

public void addFragment(Fragment currentFragment, Fragment targetFragment, String tag) {
    FragmentManager fragmentManager = getSupportFragmentManager();
    FragmentTransaction transaction = fragmentManager.beginTransaction();
    transaction.setCustomAnimations(0,0,0,0);
    transaction.hide(currentFragment);
    // use a fragment tag, so that later on we can find the currently displayed fragment
    transaction.add(R.id.frame_layout, targetFragment, tag)
            .addToBackStack(tag)
            .commit();
}

I use this method in my fragment (containing the list view) whenever a list item is clicked/tapped (and thus I need to launch/display the details fragment):

FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
SearchFragment currentFragment = (SearchFragment) fragmentManager.findFragmentByTag(getFragmentTags()[0]);
DetailsFragment detailsFragment = DetailsFragment.newInstance("some object containing some details");
((MainActivity) getActivity()).addFragment(currentFragment, detailsFragment, "Details");

getFragmentTags() returns an array of strings that I use as tags for different fragments when I add a new fragment (see transaction.add method in addFragment method above).

In the fragment containing the list view, I do this in its onPause() method:

@Override
public void onPause() {
    // keep the list view's state in memory ("save" it) 
    // before adding a new fragment or replacing current fragment with a new one
    ListView lv =  (ListView) getActivity().findViewById(R.id.listView);
    mListViewState = lv.onSaveInstanceState();
    super.onPause();
}

Then in onCreateView of the fragment (actually in a method that is invoked in onCreateView), I restore the state:

// Restore previous state (including selected item index and scroll position)
if(mListViewState != null) {
    Log.d(TAG, "Restoring the listview's state.");
    lv.onRestoreInstanceState(mListViewState);
}
Fukuoka answered 29/6, 2015 at 20:16 Comment(0)
L
0

In the end after trying many of these complicated solutions as I only needed to save/restore a single value in my Fragment (the content of an EditText), and although it might not be the most elegant solution, creating a SharedPreference and storing my state there worked for me

Lax answered 25/5, 2016 at 21:16 Comment(0)
H
0

A simple way of keeping the values of fields in different fragments in an activity

Create the Instances of fragments and add instead of replace and remove

    FragA  fa= new FragA();
    FragB  fb= new FragB();
    FragC  fc= new FragB();
    fragmentManager = getSupportFragmentManager();
    fragmentTransaction = fragmentManager.beginTransaction();
    fragmentTransaction.add(R.id.fragmnt_container, fa);
    fragmentTransaction.add(R.id.fragmnt_container, fb);
    fragmentTransaction.add(R.id.fragmnt_container, fc);
    fragmentTransaction.show(fa);
    fragmentTransaction.hide(fb);
    fragmentTransaction.hide(fc);
    fragmentTransaction.commit();

Then just show and hide the fragments instead of adding and removing those again

    fragmentTransaction = fragmentManager.beginTransaction();
    fragmentTransaction.hide(fa);
    fragmentTransaction.show(fb);
    fragmentTransaction.hide(fc);
    fragmentTransaction.commit()

;

Hobart answered 8/1, 2020 at 12:53 Comment(0)
G
-1
private ViewPager viewPager;
viewPager = (ViewPager) findViewById(R.id.pager);
mAdapter = new TabsPagerAdapter(getSupportFragmentManager());
viewPager.setAdapter(mAdapter);
viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {

        @Override
        public void onPageSelected(int position) {
            // on changing the page
            // make respected tab selected
            actionBar.setSelectedNavigationItem(position);
        }

        @Override
        public void onPageScrolled(int arg0, float arg1, int arg2) {
        }

        @Override
        public void onPageScrollStateChanged(int arg0) {
        }
    });
}

@Override
public void onTabReselected(Tab tab, FragmentTransaction ft) {
}

@Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
    // on tab selected
    // show respected fragment view
    viewPager.setCurrentItem(tab.getPosition());
}

@Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
}
Gunny answered 9/12, 2014 at 13:59 Comment(1)
Please consider including some information about your answer, rather than simply posting code. We try to provide not just 'fixes', but help people learn. You should explain what was wrong in the original code, what you did differently, and why your change(s) worked.Bunsen

© 2022 - 2024 — McMap. All rights reserved.