12

I am trying to save fragment state in onSaveInstanceState but when i go back to fragment, it always reloaded again instead of starting from last state.

I looked into onCreateView and onActivityCreated and it always have onSaveInstanceState as null.

public void navigateFragment(String tag, Fragment fragment,
        boolean shouldAdd) {

    FragmentManager manager = getSupportFragmentManager();
    FragmentTransaction ft = manager.beginTransaction();


    if (shouldAdd)
        mStacks.get(tag).push(fragment); // push fragment on stack

    ft.replace(android.R.id.tabcontent, fragment);

    if (shouldAdd)
        ft.addToBackStack(tag);

    ft.commit();

    }

As i am unable to use backstack because in tabs back stack is not useful. Any help would be highly appreciated.

moDev
  • 5,248
  • 4
  • 33
  • 63

7 Answers7

6

In this case you have to manage fragments' states by yourself. I don't know exactly how your code works so the only thing I can do is to give you some hints.

The first thing you need to implement is saving fragment's state. Let's assume that all fragments have unique ids. In this case you need to create a map that will keep all the states:

private final Map<String, Fragment.SavedState> mFragmentStates = new HashMap<>();

private void saveFragmentState(String id, Fragment fragment) {
    Fragment.SavedState fragmentState = 
            getSupportFragmentManager().saveFragmentInstanceState(fragment);
    mFragmentStates.put(id, fragmentState);
}

You need to call this method for a fragment that you're going to remove. Then we need to restore fragment's state and that's how we can do it:

private void restoreFragmentState(String id, Fragment fragment) {
    Fragment.SavedState fragmentState = mFragmentStates.remove(id);
    if (fragmentState != null) {
        fragment.setInitialSavedState(savedState);
    }
}

This method you need to call before adding a fragment to a transaction.

The code provided should work fine but to make it work correctly on activity recreation we need to save and restore mFragmentStates properly:

private static final String KEY_FRAGMENT_STATES = "fragment_states";

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    /* Your code ... */

    if (savedInstanceState != null) {
        Bundle fragmentStates =
                savedInstanceState.getParcelable(KEY_FRAGMENT_STATES);
        if (fragmentStates != null) {
            for (String id : fragmentStates.keySet()) {
                Fragment.SavedState fragmentState =
                        fragmentStates.getParcelable(id);
                mFragmentStates.put(id, fragmentState);
            }
        }
    }
}

@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    /* Your code ... */

    Bundle fragmentStates = new Bundle(mFragmentStates.size());
    for (Map.Entry<String, Fragment.SavedState> entry : mFragmentStates.entrySet()) {
        fragmentStates.put(entry.getKey(), entry.getValue());
    }
    outState.putParcelable(KEY_FRAGMENT_STATES, fragmentStates);
}

Also you can take a look at FragmentStatePagerAdapter class. It uses the same approach for managing states of ViewPager's fragments.

UPDATE: And so your code should end up looking something like this:

private Fragment mCurrentFragment;

public void navigateFragment(String tag, Fragment fragment,
        boolean shouldAdd) {
    FragmentManager manager = getSupportFragmentManager();
    FragmentTransaction transaction = manager.beginTransaction();

    if (shouldAdd) {
        mStacks.get(tag).push(fragment); // push fragment on stack
    }

    if (mCurrentFragment != null) {
        saveFragmentState(mCurrentFragment.getClass().getName(), mCurrentFragment);
    }

    mCurrentFragment = fragment;
    restoreFragmentState(fragment.getClass().getName(), fragment);
    transaction.replace(android.R.id.tabcontent, fragment);

    if (shouldAdd) {
        // You shouldn't use back-stack when managing fragment states by yourself.
        transaction.addToBackStack(tag);
    }

    transaction.commit();
}

In this example I use fragment's class name as an id so all the fragment must have different classes. But you can use any other unique value as an id. And another important thing I have to mention is that you shouldn't use back-stack when managing fragment states by yourself. Back-stack performs similar state management and you will likely have conflicts.

Michael
  • 53,859
  • 22
  • 133
  • 139
  • Your solution is interesting and seems complicated. Why not create a class extending FragmentStatePagerAdapter (from your hint)? And then of course, use it in the navigateFragment(). – The Original Android Jul 08 '15 at 08:44
  • 1
    `FragmentStatePagerAdapter` is an adapter for a `ViewPager`. It creates and manages a `FragmentTransaction` and is not designed for using in custom fragment management scenarios. Moreover, I hope this answer can be used for educational purposes. – Michael Jul 08 '15 at 10:48
  • Thanks for the answer. Let me try – moDev Jul 08 '15 at 19:03
  • I am getting error on this line `fragment.setInitialSavedState(savedState);` – moDev Jul 08 '15 at 19:19
  • `mFragmentStates.put(id, fragmentState);` This line throws error too.. I have all my code in fragment activity. Do i need to keep in fragment or fragment activity?? – moDev Jul 08 '15 at 19:21
  • I have sorted out the above two errors.. When i check in fragment it shows savedInstanceState is not null.. How i can access the fragment or do i need to save data??? – moDev Jul 08 '15 at 19:47
  • @droid_dev, sorry, but I do not understand what problems you have at the moment. Could you explain what's happening in more details? – Michael Jul 09 '15 at 08:17
  • I have added above code in fragment activity and when i check onSavedInstanceState it is not null i.e Fragment state is getting saved. How i can load the last fragment state or i need to save all my data?? – moDev Jul 09 '15 at 10:46
  • The `restoreFragmentState()` method can be used for restoring fragment's state. You must call this method before the fragment is attached to the activity. – Michael Jul 09 '15 at 10:50
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/82808/discussion-between-droid-dev-and-michael). – moDev Jul 09 '15 at 11:20
  • I have added all the code in fragment activity but still fragment is empty when we go back to fragment. Do i need to add all code on fragment activity or fragment itself?? – moDev Jul 09 '15 at 15:24
  • `private void restoreFragmentState(String id, Fragment fragment) { Fragment.SavedState fragmentState = mFragmentStates.remove(id); if (fragmentState != null) { fragment.setInitialSavedState(savedState); } } `Here `savedState` shows error.. – moDev Jul 09 '15 at 18:59
  • @Michael can you help? – moDev Jul 15 '15 at 10:29
  • @droid_dev, yes, I answered you in the chat a few days ago. I need to know what kind of error occurs in this code. – Michael Jul 15 '15 at 11:09
  • I am unable to get the last fragment using the code specified – moDev Jul 15 '15 at 12:01
  • @Michael Above code was working fine but now it crashes. It looks like error is related to fragment restore. can you help? – moDev Oct 03 '15 at 09:32
  • @droid_dev It would be better if you write me in chat and attach a stacktrace. – Michael Oct 04 '15 at 09:55
  • @Michael In which chat room, are you available? – moDev Oct 04 '15 at 21:09
  • @droid_dev Actually I don't know why it happens. I think the best way to solve this problem would be to ask a new question with this stacktrace and your code attached. – Michael Oct 05 '15 at 09:53
  • @Michael Thanks for looking. I got the issue. I was trying to restore only selected fragments. If i uncomment those lines then it works. can you help how to restore/save only selected fragments?? – moDev Oct 05 '15 at 10:00
  • I was trying to restore/save selected fragment `if (mCurrentFragment != null) { if (mCurrentFragment instanceof GetProducts) saveFragmentState(mCurrentFragment.getClass().getName(), mCurrentFragment); } mCurrentFragment = fragment; if (fragment instanceof GetProducts) restoreFragmentState(fragment.getClass().getName(), fragment);` – moDev Oct 05 '15 at 10:02
  • @droid_dev I mean create a new question on StackOverflow :) You may post a link to this question here so I will be able to try and help you. – Michael Oct 05 '15 at 10:24
0

I think you should use the idea of Shared Preferences, this is faster than using local files or database, and definitely easier to code. A good Android webpage @ Storage Options. I'll put some sample code from the webpage and change a few to fit your needs.

First, get the data saved from preferences in onCreate() override method:

@Override
public void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);

   SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);
   boolean silent = settings.getBoolean("silentMode", false);
...
}

Next let's save data before the fragment stops or exits, for various reasons. Achieve this by override onDetach method, the webpage override onStop() instead.

@Override
public void onDetach() {
   super.onDetach();
   ...
   SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);
   SharedPreferences.Editor editor = settings.edit();
   editor.putBoolean("silentMode", mSilentMode);

   // Commit the edits!
   editor.commit();
}

Good luck, have fun!

The Original Android
  • 6,147
  • 3
  • 26
  • 31
0

You are doing replace fragment, thats why fragment is getting destroyed. You can try below. Here i am doing two things in single FragmentTransaction, i am adding a new fragment & hiding the existing fragment.

Lets say we are adding fragment B on top of Fragment A

    FragmentTransaction ft = fragmentManager.beginTransaction();
    ft.add(android.R.id.tabcontent, fragmentB, tagFragmentB)
            .hide(fragmentManager.findFragmentByTag(tagFragmentA))
            .addToBackStack(tag)
            .commit();

Once you do back press, it will remove the fragment B & show the fragment A with same state(before you added fragment B)

Please note here tagFragmentA & tagFragmentB are tags with which fragments A & B are added respectively

Akhil
  • 6,667
  • 4
  • 31
  • 61
0

I am not clear of way kind of vie you are using for implementing Tabs. I guessed from,

ft.replace(android.R.id.tabcontent, fragment);

that you might have implemented FragmentTabHost.

write a customFragmentTabhost and override

 @Override
public void onTabChanged(String tabId) {
    FragmentTransaction t = getSupportFragmentManager().beginTransaction();
    if (tabId.equals(“Tab1”)) {
        TabFragment1 fragment1 = null;
        if (getSupportFragmentManager().findFragmentByTag(“Tab1”) == null) {
            fragment1 = new TabFragment1();
        } else {
            fragment1 = (TabFragment1) getSupportFragmentManager().findFragmentByTag("Tab1");
        }
        t.replace(R.id.realContent, fragment1, "Tab1").addToBackStack(null).commit();
    }
}

UPDATE:

  1. Ensure Activity is not recreated on orientation, If so setRetainInstance(true), so that even if activity is recreated on orientation change, the fragments will be retained.

  2. Do give id for any views in the fragment. It is important for Android system to maintain state.

Sowmia Sundararajan
  • 1,613
  • 2
  • 14
  • 17
0
      clearBackStackEntry();
                        rl.setVisibility(View.GONE);

                        getSupportFragmentManager().beginTransaction()
                                .replace(FRAGMENT_CONTAINER, new HomeScreen())
                                .addToBackStack(null).commit();

     private void clearBackStackEntry() {
            int count = getSupportFragmentManager().getBackStackEntryCount();
            if (count > 0) {
                getSupportFragmentManager().popBackStack(null,
                        FragmentManager.POP_BACK_STACK_INCLUSIVE);
            }
        }



TRY THIS AND One more for fragment also try this: but use support.v4.app.Fragment, May be it will help you


Fragment fr = new main();
                android.support.v4.app.FragmentTransaction fragmentTransaction = getFragmentManager()
                        .beginTransaction();
                fragmentTransaction.replace(R.id.fragment_place, fr);
                fragmentTransaction.commit();
                // getActivity().finish();
parik dhakan
  • 787
  • 4
  • 19
0

declare

setRetainInstance(true) 

In your fragment onCreate(). to recreate previous state
And link will helpful for understanding for how setRetainInstance work.

Understanding Fragment's setRetainInstance(boolean)

Why use Fragment#setRetainInstance(boolean)?

Thanks :)

Community
  • 1
  • 1
Lokesh
  • 3,247
  • 2
  • 33
  • 62
0

it is very simple to handle these things. I can give you the sample to handle the back press on Fr agents which we added.

I have declared a fragment stack and push all the fragments in it like;

public static Stack<Fragment> fragmentStack;

make a method like this:

    public static void replaceFragementsClick(Fragment fragementObj,     Bundle bundleObj, String title){
        try {
            FragmentManager fragmentManager = ((FragmentActivity)     mContext).getSupportFragmentManager();
            if (fragementObj != null) {

            fragementObj.setArguments(bundleObj);
                fragmentManager.beginTransaction().replace(R.id.frame_container,     fragementObj).commit();
            } 

            DashBoardActivity.fragmentStack.push(fragementObj);


        } catch (Exception e) {
            e.printStackTrace();
        }
    }

Try this one also:

    public static void replaceFragementsClickBack(Fragment fragementObj, Bundle bundleObj, String title){
        try {
            FragmentManager fragmentManager = ((FragmentActivity) mContext).getSupportFragmentManager();
            if (fragementObj != null) {

            fragementObj.setArguments(bundleObj);
                fragmentManager.beginTransaction().replace(R.id.frame_container,     fragementObj).commit();

                DashBoardActivity.fragmentStack.pop();
            } 
        } catch (Exception e) {
            e.printStackTrace();
        }
}

In the base activity where you have added, override the backpressed like:

@Override
    public void onBackPressed() {
            /**
             * Do Current Fragment Pop
             * */           
            fragmentStack.pop();            

            if(fragmentStack.size() >0){

                Bundle bunldeObj = new Bundle();
                //******Exit from Current Fragment
               Fragment fragment = fragmentStack.pop(); 
//                  fragmentStack.push(fragment);
                    if(fragment instanceof PhotosFragment){
                    bunldeObj.putString("position", "4");               
                    replaceFragementsClick(fragment,bunldeObj,"Photos");
                }else if(fragment instanceof PhotoDetatilFragment){
                    bunldeObj.putString("position", "4");
                    replaceFragementsClick(fragment,bunldeObj,"Photos");
                }else if(fragment instanceof PhotoFullViewFragment){
                    bunldeObj.putString("position", "4");
                    replaceFragementsClick(fragment,bunldeObj,"Photos");
                }else if(fragment instanceof HomeFragment){
                    bunldeObj.putString("position", "4");
                    replaceFragementsClick(fragment,bunldeObj,"Home");
                }else if(fragment instanceof VideosFragment){
                    bunldeObj.putString("position", "4");
                    replaceFragementsClick(fragment,bunldeObj,"Videos");
                }else if(fragment instanceof VideoDetailFragment){
                    bunldeObj.putString("position", "4");
                    replaceFragementsClick(fragment,bunldeObj,"Videos");
                }else if(fragment instanceof VideoViewFragment){
                    bunldeObj.putString("position", "4");
                    replaceFragementsClick(fragment,bunldeObj,"Videos");
                }else if(fragment instanceof MusicFragment){
                    bunldeObj.putString("position", "4");
                    replaceFragementsClick(fragment,bunldeObj,"Music");
                }else if(fragment instanceof MusicListFragment){
                    bunldeObj.putString("position", "4");
                    replaceFragementsClick(fragment,bunldeObj,"Music");
                }else if(fragment instanceof InstallAppsFragment){
                    bunldeObj.putString("position", "4");
                    replaceFragementsClick(fragment,bunldeObj,"Apps");
                }else if(fragment instanceof MessageFragment){
                    bunldeObj.putString("position", "4");
                        replaceFragementsClick(fragment,bunldeObj,"Messages");
                }else if(fragment instanceof MessageDetailFragment){
                    bunldeObj.putString("position", "4");
                    replaceFragementsClick(fragment,bunldeObj,"Messages");
                }else if(fragment instanceof LocateDeviceFragment){
                    bunldeObj.putString("position", "4");
                    replaceFragementsClick(fragment,bunldeObj,"Locate     Device");
                }else if(fragment instanceof FilesFragmentBottomBar){
                    bunldeObj.putString("position", "4");
                    replaceFragementsClick(fragment,bunldeObj,"Files");
                }else if(fragment instanceof AppsFragment){
                    bunldeObj.putString("position", "4");
                    replaceFragementsClick(fragment,bunldeObj,"Apps");  



            }else {
                super.onBackPressed();

                Intent intent = new     Intent(DashBoardActivity.this,ConnectDeviceActivity.class);
                startActivity(intent);
                finish();
        }
    }
hitesh141
  • 963
  • 12
  • 25