Android - save/restore fragment state

AndroidAndroid FragmentsFragmentOnresumeBack Stack

Android Problem Overview


I have an Activity in which I go through several fragments. In every fragment I have several views (EditText, ListView, Map, etc).

How can I save the instance of the fragment that is shown at that moment? I need it to work when the activity is onPause() --> onResume(). Also I need it to work when I return from another fragment (pop from backstack).

From the main Activity I call the first fragment, then from the the fragment I call the next one.

Code for my Activity:

public class Activity_Main extends FragmentActivity{

public static Fragment_1 fragment_1;
public static Fragment_2 fragment_2;
public static Fragment_3 fragment_3;
public static FragmentManager fragmentManager;

@Override
 protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.main);

     fragment_1 = new Fragment_1();

     fragment_2 = new Fragment_2();

     fragment_3 = new Fragment_3();

     fragmentManager = getSupportFragmentManager();
     FragmentTransaction transaction_1 = fragmentManager.beginTransaction();
     transaction_1.replace(R.id.content_frame, fragment_1);
     transaction_1.commit();
}}

Then here is the code for one of my fragments:

public class Fragment_1 extends Fragment {

      private EditText title;
      private Button go_next;


      @Override
      public View onCreateView(final LayoutInflater inflater,
		ViewGroup container, Bundle savedInstanceState) {

	        View rootView = inflater.inflate(R.layout.fragment_1,
			container, false);

            title = (EditText) rootView.findViewById(R.id.title);

            go_next = (Button) rootView.findViewById(R.id.go_next);

            image.setOnClickListener(new View.OnClickListener() {

	     @Override
	     public void onClick(View v) {

                 FragmentTransaction transaction_2 = Activity_Main.fragmentManager
				.beginTransaction();
		
	    	     transaction_2.replace(R.id.content_frame,
				  Activity_Main.fragment_2);
	    	     transaction_2.addToBackStack(null);
	     	     transaction_2.commit();  
               
            });
        }}

I have searched a lot of information but nothing clear. Can somebody give a clear solution and an example, please ?

Android Solutions


Solution 1 - Android

When a fragment is moved to the backstack, it isn't destroyed. All the instance variables remain there. So this is the place to save your data. In onActivityCreated you check the following conditions:

  1. Is the bundle != null? If yes, that's where the data is saved (probably orientation change).
  2. Is there data saved in instance variables? If yes, restore your state from them (or maybe do nothing, because everything is as it should be).
  3. Otherwise your fragment is shown for the first time, create everything anew.

Edit: Here's an example

public class ExampleFragment extends Fragment {
	private List<String> myData;

	@Override
	public void onSaveInstanceState(final Bundle outState) {
		super.onSaveInstanceState(outState);
		outState.putSerializable("list", (Serializable) myData);
	}
	
	@Override
	public void onActivityCreated(Bundle savedInstanceState) {
		super.onActivityCreated(savedInstanceState);
		
		if (savedInstanceState != null) {
			//probably orientation change
			myData = (List<String>) savedInstanceState.getSerializable("list");
		} else {
			if (myData != null) {
				//returning from backstack, data is fine, do nothing
			} else {
				//newly created, compute data
				myData = computeData();
			}
		}
	}
}

Solution 2 - Android

Android fragment has some advantages and some disadvantages. The most disadvantage of the fragment is that when you want to use a fragment you create it ones. When you use it, onCreateView of the fragment is called for each time. If you want to keep state of the components in the fragment you must save fragment state and yout must load its state in the next shown. This make fragment view a bit slow and weird.

I have found a solution and I have used this solution: "Everything is great. Every body can try".

When first time onCreateView is being run, create view as a global variable. When second time you call this fragment onCreateView is called again you can return this global view. The fragment component state will be kept.

View view;

@Override
public View onCreateView(LayoutInflater inflater,
		@Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    setActionBar(null);
    if (view != null) {
        if ((ViewGroup)view.getParent() != null)
            ((ViewGroup)view.getParent()).removeView(view);
        return view; 
    }
    view = inflater.inflate(R.layout.mylayout, container, false);
}

Solution 3 - Android

Try this :

@Override
protected void onPause() {
    super.onPause();
    if (getSupportFragmentManager().findFragmentByTag("MyFragment") != null)
        getSupportFragmentManager().findFragmentByTag("MyFragment").setRetainInstance(true);
}

@Override
protected void onResume() {
    super.onResume();
    if (getSupportFragmentManager().findFragmentByTag("MyFragment") != null)
        getSupportFragmentManager().findFragmentByTag("MyFragment").getRetainInstance();
}

Hope this will help.

Also you can write this to activity tag in menifest file :

  android:configChanges="orientation|screenSize"

Good luck !!!

Solution 4 - Android

In order to save the Fragment state you need to implement onSaveInstanceState(): "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(). For more information about saving state, see the Activities document."

http://developer.android.com/guide/components/fragments.html#Lifecycle

Solution 5 - Android

As stated here: https://stackoverflow.com/questions/11160412/why-use-fragmentsetretaininstanceboolean

you can also use fragments method setRetainInstance(true) like this:

public class MyFragment extends Fragment {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // keep the fragment and all its data across screen rotation
        setRetainInstance(true);

    }
}

Solution 6 - Android

You can get current Fragment from fragmentManager. And if there are non of them in fragment manager you can create Fragment_1

public class MainActivity extends FragmentActivity {
	
	
	public static Fragment_1 fragment_1;
	public static Fragment_2 fragment_2;
	public static Fragment_3 fragment_3;
	public static FragmentManager fragmentManager;
	

	@Override
	protected void onCreate(Bundle arg0) {
		super.onCreate(arg0);
		setContentView(R.layout.main);
		
		fragment_1 = (Fragment_1) fragmentManager.findFragmentByTag("fragment1");
		
		fragment_2  =(Fragment_2) fragmentManager.findFragmentByTag("fragment2");
		
		fragment_3 = (Fragment_3) fragmentManager.findFragmentByTag("fragment3");
		
		
		if(fragment_1==null && fragment_2==null && fragment_3==null){			
			fragment_1 = new Fragment_1();			
			fragmentManager.beginTransaction().replace(R.id.content_frame, fragment_1, "fragment1").commit();
		}
		
		
	}
	
	
}

also you can use setRetainInstance to true what it will do it ignore onDestroy() method in fragment and your application going to back ground and os kill your application to allocate more memory you will need to save all data you need in onSaveInstanceState bundle

public class Fragment_1 extends Fragment {

	
	private EditText title;
    private Button go_next;
	
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setRetainInstance(true); //Will ignore onDestroy Method (Nested Fragments no need this if parent have it)
	}
	
	
	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container,
			Bundle savedInstanceState) {
		onRestoreInstanceStae(savedInstanceState);
		return super.onCreateView(inflater, container, savedInstanceState);
	}
	
	
	//Here you can restore saved data in onSaveInstanceState Bundle
	private void onRestoreInstanceState(Bundle savedInstanceState){
		if(savedInstanceState!=null){
			String SomeText = savedInstanceState.getString("title");			
		}
	}
	
	//Here you Save your data
	@Override
	public void onSaveInstanceState(Bundle outState) {
		super.onSaveInstanceState(outState);
		outState.putString("title", "Some Text");
	}
	
}

Solution 7 - Android

I'm not quite sure if this question is still bothering you, since it has been several months. But I would like to share how I dealt with this. Here is the source code:

int FLAG = 0;
private View rootView;
private LinearLayout parentView;

/**
 * The fragment argument representing the section number for this fragment.
 */
private static final String ARG_SECTION_NUMBER = "section_number";

/**
 * Returns a new instance of this fragment for the given section number.
 */
public static Fragment2 newInstance(Bundle bundle) {
	Fragment2 fragment = new Fragment2();
	Bundle args = bundle;
	fragment.setArguments(args);
	return fragment;
}

public Fragment2() {

}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
	super.onCreateView(inflater, container, savedInstanceState);
	Log.e("onCreateView","onCreateView");
	if(FLAG!=12321){
		rootView = inflater.inflate(R.layout.fragment_create_new_album, container, false);
		changeFLAG(12321);
	}		
	parentView=new LinearLayout(getActivity());
	parentView.addView(rootView);
	
	return parentView;
}

/* (non-Javadoc)
 * @see android.support.v4.app.Fragment#onDestroy()
 */
@Override
public void onDestroy() {
	// TODO Auto-generated method stub
	super.onDestroy();
	Log.e("onDestroy","onDestroy");
}

/* (non-Javadoc)
 * @see android.support.v4.app.Fragment#onStart()
 */
@Override
public void onStart() {
	// TODO Auto-generated method stub
	super.onStart();
	Log.e("onstart","onstart");
}

/* (non-Javadoc)
 * @see android.support.v4.app.Fragment#onStop()
 */
@Override
public void onStop() {
	// TODO Auto-generated method stub
	super.onStop();
	if(false){
		Bundle savedInstance=getArguments();
		LinearLayout viewParent;
		
		viewParent= (LinearLayout) rootView.getParent();
		viewParent.removeView(rootView);
		
	}
	parentView.removeView(rootView);
	
	Log.e("onStop","onstop");
}
@Override
public void onPause() {
	super.onPause();
	Log.e("onpause","onpause");
}

@Override
public void onResume() {
	super.onResume();
	Log.e("onResume","onResume");
}

And here is the MainActivity:

/**
 * Fragment managing the behaviors, interactions and presentation of the
 * navigation drawer.
 */
private NavigationDrawerFragment mNavigationDrawerFragment;

/**
 * Used to store the last screen title. For use in
 * {@link #restoreActionBar()}.
 */

public static boolean fragment2InstanceExists=false;
public static Fragment2 fragment2=null;

@Override
protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
	setContentView(R.layout.activity_main);

	mNavigationDrawerFragment = (NavigationDrawerFragment) getSupportFragmentManager()
			.findFragmentById(R.id.navigation_drawer);
	mTitle = getTitle();

	// Set up the drawer.
	mNavigationDrawerFragment.setUp(R.id.navigation_drawer,
			(DrawerLayout) findViewById(R.id.drawer_layout));
}

@Override
public void onNavigationDrawerItemSelected(int position) {
	// update the main content by replacing fragments
	FragmentManager fragmentManager = getSupportFragmentManager();
	FragmentTransaction fragmentTransaction=fragmentManager.beginTransaction();
	switch(position){
	case 0:
		fragmentTransaction.addToBackStack(null);
		fragmentTransaction.replace(R.id.container, Fragment1.newInstance(position+1)).commit();
		break;
	case 1:
		
		Bundle bundle=new Bundle();
		bundle.putInt("source_of_create",CommonMethods.CREATE_FROM_ACTIVITY);
		
		if(!fragment2InstanceExists){
			fragment2=Fragment2.newInstance(bundle);
			fragment2InstanceExists=true;
		}
		fragmentTransaction.addToBackStack(null);
		fragmentTransaction.replace(R.id.container, fragment2).commit();
		
		break;
	case 2:
		fragmentTransaction.addToBackStack(null);
		fragmentTransaction.replace(R.id.container, FolderExplorerFragment.newInstance(position+1)).commit();
		break;
	default: 
		break;
	}
}

The parentView is the keypoint. Normally, when onCreateView, we just use return rootView. But now, I add rootView to parentView, and then return parentView. To prevent "The specified child already has a parent. You must call removeView() on the ..." error, we need to call parentView.removeView(rootView), or the method I supplied is useless. I also would like to share how I found it. Firstly, I set up a boolean to indicate if the instance exists. When the instance exists, the rootView will not be inflated again. But then, logcat gave the child already has a parent thing, so I decided to use another parent as a intermediate Parent View. That's how it works.

Hope it's helpful to you.

Solution 8 - Android

If you using bottombar and insted of viewpager you want to set custom fragment replacement logic with retrieve previously save state you can do using below code

 String current_frag_tag = null;
 String prev_frag_tag = null;



    @Override
    public void onTabSelected(TabLayout.Tab tab) {
   

        switch (tab.getPosition()) {
            case 0:

                replaceFragment(new Fragment1(), "Fragment1");
                break;

            case 1:
                replaceFragment(new Fragment2(), "Fragment2");
                break;

            case 2:
                replaceFragment(new Fragment3(), "Fragment3");
                break;

            case 3:
               replaceFragment(new Fragment4(), "Fragment4");
                break;

            default:
                replaceFragment(new Fragment1(), "Fragment1");
                break;

        }

    public void replaceFragment(Fragment fragment, String tag) {
        if (current_frag_tag != null) {
            prev_frag_tag = current_frag_tag;
        }

        current_frag_tag = tag;


        FragmentManager manager = null;
        try {
            manager = requireActivity().getSupportFragmentManager();
            FragmentTransaction ft = manager.beginTransaction();

            if (manager.findFragmentByTag(current_frag_tag) == null) { // No fragment in backStack with same tag..
                ft.add(R.id.viewpagerLayout, fragment, current_frag_tag);

                if (prev_frag_tag != null) {
                    try {
                        ft.hide(Objects.requireNonNull(manager.findFragmentByTag(prev_frag_tag)));
                    } catch (NullPointerException e) {
                        e.printStackTrace();
                    }
                }
//            ft.show(manager.findFragmentByTag(current_frag_tag));
                ft.addToBackStack(current_frag_tag);
                ft.commit();

            } else {

                try {
                    ft.hide(Objects.requireNonNull(manager.findFragmentByTag(prev_frag_tag)))
                            .show(Objects.requireNonNull(manager.findFragmentByTag(current_frag_tag))).commit();
                } catch (NullPointerException e) {
                    e.printStackTrace();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }




    }

>Inside Child Fragments you can access fragment is visible or not using below method note: you have to implement below method in child fragment

@Override
    public void onHiddenChanged(boolean hidden) {
        super.onHiddenChanged(hidden);

        try {
            if(hidden){
                adapter.getFragment(mainVideoBinding.viewPagerVideoMain.getCurrentItem()).onPause();
            }else{
                adapter.getFragment(mainVideoBinding.viewPagerVideoMain.getCurrentItem()).onResume();
            }
        }catch (Exception e){
       }

    }

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionStaneteView Question on Stackoverflow
Solution 1 - AndroidKirill RakhmanView Answer on Stackoverflow
Solution 2 - AndroidnebyanView Answer on Stackoverflow
Solution 3 - AndroidSarView Answer on Stackoverflow
Solution 4 - AndroidRaananView Answer on Stackoverflow
Solution 5 - AndroidRichard RView Answer on Stackoverflow
Solution 6 - AndroidConstantin CerberusView Answer on Stackoverflow
Solution 7 - AndroidbutchView Answer on Stackoverflow
Solution 8 - AndroidManthan PatelView Answer on Stackoverflow