Handling Configuration Changes

Overview

There are various situations such as when the screen orientation is rotated where the Activity can actually be destroyed and removed from memory and then re-created from scratch again. In these situations, the best practice is to prepare for cases where the Activity is re-created by properly saving and restoring the state.

Saving and Restoring Activity State

If a user navigates to a different away from the activity, the onPause() and onResume() methods are called. If you need to retain state information in those cases, it's best to save state through the use of Shared Preferences:

Note that onSaveInstanceState() is called right before your activity is about to be killed or restarted because of memory pressure or screen orientation. This is different from onPause() which gets called when your activity loses focus (i.e. you transition to another activity). The default implementation of this method automatically saves information about the state of the activity's view hierarchy, such as the text in an EditText widget or the scroll position of a ListView. For other data to persist, you can put the data in the Bundle provided.

publicclassMainActivityextendsActivity{staticfinalStringSOME_VALUE="int_value";staticfinalStringSOME_OTHER_VALUE="string_value";@OverrideprotectedvoidonSaveInstanceState(BundlesavedInstanceState){// Save custom values into the bundlesavedInstanceState.putInt(SOME_VALUE,someIntValue);savedInstanceState.putString(SOME_OTHER_VALUE,someStringValue);// Always call the superclass so it can save the view hierarchy statesuper.onSaveInstanceState(savedInstanceState);}}

The system will call that method before an Activity is destroyed. Then later the system will call onRestoreInstanceState where we can restore state from the bundle:

@OverrideprotectedvoidonRestoreInstanceState(BundlesavedInstanceState){// Always call the superclass so it can restore the view hierarchysuper.onRestoreInstanceState(savedInstanceState);// Restore state members from saved instancesomeIntValue=savedInstanceState.getInt(SOME_VALUE);someStringValue=savedInstanceState.getString(SOME_OTHER_VALUE);}

Instance state can also be restored in the standard Activity#onCreate method but it is convenient to do it in onRestoreInstanceState which ensures all of the initialization has been done and allows subclasses to decide whether to use the default implementation. Read this stackoverflow post for details.

Note that onSaveInstanceState and onRestoreInstanceState are not guaranteed to be called together. Android invokes onSaveInstanceState() when there's a chance the activity might be destroyed. However, there are cases where onSaveInstanceState is called but the activity is not destroyed and as a result onRestoreInstanceState is not invoked.

Saving and Restoring Fragment State

Fragments also have a onSaveInstanceState() method which is called when their state needs to be saved:

publicclassMySimpleFragmentextendsFragment{privateintsomeStateValue;privatefinalStringSOME_VALUE_KEY="someValueToSave";// Fires when a configuration change occurs and fragment needs to save state@OverrideprotectedvoidonSaveInstanceState(BundleoutState){outState.putInt(SOME_VALUE_KEY,someStateValue);super.onSaveInstanceState(outState);}}

Then we can pull data out of this saved state in onCreateView:

publicclassMySimpleFragmentextendsFragment{// ...// Inflate the view for the fragment based on layout XML@OverridepublicViewonCreateView(LayoutInflaterinflater,ViewGroupcontainer,BundlesavedInstanceState){Viewview=inflater.inflate(R.layout.my_simple_fragment,container,false);if(savedInstanceState!=null){someStateValue=savedInstanceState.getInt(SOME_VALUE_KEY);// Do something with value if needed}returnview;}}

For the fragment state to be saved properly, we need to be sure that we aren't unnecessarily recreating the fragment on configuration changes. This means being careful not to reinitialize existing fragments when they already exist. Any fragments being initialized in an Activity need to be looked up by tag after a configuration change:

publicclassParentActivityextendsAppCompatActivity{privateMySimpleFragmentfragmentSimple;privatefinalStringSIMPLE_FRAGMENT_TAG="myfragmenttag";@OverrideprotectedvoidonCreate(BundlesavedInstanceState){if(savedInstanceState!=null){// saved instance state, fragment may exist// look up the instance that already exists by tagfragmentSimple=(MySimpleFragment)getSupportFragmentManager().findFragmentByTag(SIMPLE_FRAGMENT_TAG);}elseif(fragmentSimple==null){// only create fragment if they haven't been instantiated alreadyfragmentSimple=newMySimpleFragment();}}}

This requires us to be careful to include a tag for lookup whenever putting a fragment into the activity within a transaction:

publicclassParentActivityextendsAppCompatActivity{privateMySimpleFragmentfragmentSimple;privatefinalStringSIMPLE_FRAGMENT_TAG="myfragmenttag";@OverrideprotectedvoidonCreate(BundlesavedInstanceState){// ... fragment lookup or instantation from above...// Always add a tag to a fragment being inserted into containerif(!fragmentSimple.isInLayout()){getSupportFragmentManager().beginTransaction().replace(R.id.container,fragmentSimple,SIMPLE_FRAGMENT_TAG).commit();}}}

With this simple pattern, we can properly re-use fragments and restore their state across configuration changes.

Retaining Fragments

In many cases, we can avoid problems when an Activity is re-created by simply using fragments. If your views and state are within a fragment, we can easily have the fragment be retained when the activity is re-created:

publicclassRetainedFragmentextendsFragment{// data object we want to retainprivateMyDataObjectdata;// this method is only called once for this fragment@OverridepublicvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);// retain this fragment when activity is re-initializedsetRetainInstance(true);}publicvoidsetData(MyDataObjectdata){this.data=data;}publicMyDataObjectgetData(){returndata;}}

This approach keeps the fragment from being destroyed during the activity lifecycle. They are instead retained inside the Fragment Manager. See the Android official docs for more information.

Now you can check to see if the fragment already exists by tag before creating one and the fragment will retain it's state across configuration changes. See the Handling Runtime Changes guide for more details.

Properly Handling List State

ListView

Often when you rotate the screen, the app will lose the scroll position and other state of any lists on screen. To properly retain this state for ListView, you can store the instance state onPause and restore onViewCreated as shown below:

// YourActivity.javaprivatestaticfinalStringLIST_STATE="listState";privateParcelablemListState=null;// Write list state to bundle@OverrideprotectedvoidonSaveInstanceState(Bundlestate){super.onSaveInstanceState(state);mListState=getListView().onSaveInstanceState();state.putParcelable(LIST_STATE,mListState);}// Restore list state from bundle@OverrideprotectedvoidonRestoreInstanceState(Bundlestate){super.onRestoreInstanceState(state);mListState=state.getParcelable(LIST_STATE);}@OverrideprotectedvoidonResume(){super.onResume();loadData();// make sure data has been reloaded into adapter first// ONLY call this part once the data items have been loaded back into the adapter// for example, inside a success callback from the networkif(mListState!=null){myListView.onRestoreInstanceState(mListState);mListState=null;}}

Note that you must load the items back into the adapter first before calling onRestoreInstanceState. In other words, don't call onRestoreInstanceState on the ListView until after the items are loaded back in from the network or the database.

RecyclerView

Often when you rotate the screen, the app will lose the scroll position and other state of any lists on screen. To properly retain this state for RecyclerView, you can store the instance state onPause and restore onViewCreated as shown below:

Locking Screen Orientation

If you want to lock the screen orientation change of any screen (activity) of your android application you just need to set the android:screenOrientation property of an <activity> within the AndroidManifest.xml:

Now that activity is forced to always be displayed in "portrait" mode.

Manually Managing Configuration Changes

If your application doesn't need to update resources during a specific configuration change and you have a performance limitation that requires you to avoid the activity restart, then you can declare that your activity handles the configuration change itself, which prevents the system from restarting your activity.

However, this technique should be considered a last resort when you must avoid restarts due to a configuration change and is not recommended for most applications. To take this approach, we must add the android:configChanges node to the activity within the AndroidManifest.xml:

Now, when one of these configurations change, the activity does not restart but instead receives a call to onConfigurationChanged():

// Within the activity which receives these changes// Checks the current device orientation, and toasts accordingly@OverridepublicvoidonConfigurationChanged(ConfigurationnewConfig){super.onConfigurationChanged(newConfig);// Checks the orientation of the screenif(newConfig.orientation==Configuration.ORIENTATION_LANDSCAPE){Toast.makeText(this,"landscape",Toast.LENGTH_SHORT).show();}elseif(newConfig.orientation==Configuration.ORIENTATION_PORTRAIT){Toast.makeText(this,"portrait",Toast.LENGTH_SHORT).show();}}

Leveraging ViewModels

Android's new Architecture Components helps manage configuration states. All state data related to the UI can be moved to a ViewModel, which will survive rotate changes because they are data tied to the application instead of Activity instance. In this way, you do not need to worry about persisting this configuration data between Activity lifecycles.

Setup

First, make sure to add the ViewModel library:

dependencies{implementation"android.arch.lifecycle:viewmodel:1.1.1"}

Your ViewModel should extend from the ViewModel class. Move for instance the adapter to be encapsulated within the ViewModel: