Preserving the state of an Android WebView on screen orientation change

Source code

If you’ve tried to use a WebView inside your app, you know that the standard behavior on screen orientation change is not satisfactory in most cases because the full state of the WebView is not preserved. Here I’m going to show you a possible implementation to keep the full state of the WebView every time you rotate the screen. This is the same implementation I used in my own app (FWebLauncher) for the internal web browser.

Standard implementation (the state is not completely preserved)

Let’s start by taking a look at what a standard implementation would look like. This is how you would usually implement the state saving for a WebView according to the Android documentation:

public class StandardImplActivity extends Activity
{
	protected WebView webView;

	@Override
	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.standard_impl);

		// Retrieve UI elements
		webView = ((WebView)findViewById(R.id.webView));
		
		// Initialize the WebView
		webView.getSettings().setSupportZoom(true);
		webView.getSettings().setBuiltInZoomControls(true);
		webView.setScrollBarStyle(WebView.SCROLLBARS_OUTSIDE_OVERLAY);
		webView.setScrollbarFadingEnabled(true);
		webView.getSettings().setLoadsImagesAutomatically(true);

		// Load the URLs inside the WebView, not in the external web browser
		webView.setWebViewClient(new WebViewClient());

		if (savedInstanceState == null)
		{
			// Load a page
			webView.loadUrl("http://www.google.com");
		}
	}
	
	@Override
	protected void onSaveInstanceState(Bundle outState)
	{
		super.onSaveInstanceState(outState);

		// Save the state of the WebView
		webView.saveState(outState);
	}
	
	@Override
	protected void onRestoreInstanceState(Bundle savedInstanceState)
	{
		super.onRestoreInstanceState(savedInstanceState);

		// Restore the state of the WebView
		webView.restoreState(savedInstanceState);
	}
}

So we call the saveState and the restoreState methods in the onSaveInstanceState and the onRestoreInstanceState methods of the Activity.

The WebView is declared directly in the layout file for the activity:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:layout_width="fill_parent"
	android:layout_height="fill_parent">
	
	<WebView android:id="@+id/webView"
		android:layout_width="fill_parent"
		android:layout_height="fill_parent"/>
	
</LinearLayout>

The main problem with this implementation is that, whenever you rotate the screen, the WebView is created again because the activity is destroyed and its saveState method doesn’t save the full state, but only a part of it like the URL of the page that was loaded and the browsing history. So it happens that for example the zoom and the scroll position are not preserved after the screen orientation change and sometimes the page is reloaded from the web.

State preserving implementation (a possible solution)

I tried many different solutions to preserve the full state of the WebView on screen rotation and the following one proved to be a reliable one that solves our problem. The main point is that the activity must not be destroyed and we must handle the screen orientation change by ourselves. Let’s start by taking a look at the layout file:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:layout_width="fill_parent"
	android:layout_height="fill_parent">
	
	<FrameLayout android:id="@+id/webViewPlaceholder"
		android:layout_width="fill_parent"
		android:layout_height="fill_parent"/>
	
</LinearLayout>

You immediately notice the difference with the standard implementation. Here we don’t declare the WebView inside the layout file, but we declare a placeholder instead. It is the position where our WebView will be placed inside the activity.

Also the code inside the activity will be different of course and here it is:

public class StatePreservingImplActivity extends Activity
{
	protected FrameLayout webViewPlaceholder;
	protected WebView webView;

	@Override
	public void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		setContentView(R.layout.state_preserving_impl);
		
		// Initialize the UI
		initUI();
	}
	
	protected void initUI()
	{
		// Retrieve UI elements
		webViewPlaceholder = ((FrameLayout)findViewById(R.id.webViewPlaceholder));

		// Initialize the WebView if necessary
		if (webView == null)
		{
			// Create the webview
			webView = new WebView(this);
			webView.setLayoutParams(new ViewGroup.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
			webView.getSettings().setSupportZoom(true);
			webView.getSettings().setBuiltInZoomControls(true);
			webView.setScrollBarStyle(WebView.SCROLLBARS_OUTSIDE_OVERLAY);
			webView.setScrollbarFadingEnabled(true);
			webView.getSettings().setLoadsImagesAutomatically(true);

			// Load the URLs inside the WebView, not in the external web browser
			webView.setWebViewClient(new WebViewClient());

			// Load a page
			webView.loadUrl("http://www.google.com");
		}

		// Attach the WebView to its placeholder
		webViewPlaceholder.addView(webView);
	}

	@Override
	public void onConfigurationChanged(Configuration newConfig)
	{
		if (webView != null)
		{
			// Remove the WebView from the old placeholder
			webViewPlaceholder.removeView(webView);
		}

		super.onConfigurationChanged(newConfig);
		
		// Load the layout resource for the new configuration
		setContentView(R.layout.state_preserving_impl);

		// Reinitialize the UI
		initUI();
	}
	
	@Override
	protected void onSaveInstanceState(Bundle outState)
	{
		super.onSaveInstanceState(outState);

		// Save the state of the WebView
		webView.saveState(outState);
	}
	
	@Override
	protected void onRestoreInstanceState(Bundle savedInstanceState)
	{
		super.onRestoreInstanceState(savedInstanceState);

		// Restore the state of the WebView
		webView.restoreState(savedInstanceState);
	}
}

We need to change also the AndroidManifest.xml file to intercept the configuration changes inside the activity:

<activity
	android:name=".StatePreservingImplActivity"
	android:configChanges="keyboard|keyboardHidden|orientation"
	android:label="State preserving implementation"/>

In the android:configChanges attribute we are telling the system that we don’t want the activity to be restarted when the declared configuration changes happen, but we want to handle the configuration changes by ourselves through the onConfigurationChanged method that will be called every time there is a change.

When the activity is created we initialize its content with the current layout and we initialize the user interface with the initUI method. That’s where the WebView is created, stored in the webView class field and then attached to the webViewPlaceholder element filling its space completely. Every time the screen is rotated, the onConfigurationChanged method is called so we remove the WebView from its current placeholder first, then we load the new activity layout (we can define a different layout for the portrait and the landscape configuration) and reinitialize the UI. After a configuration change, the activity instance is still the same because it has not been destroyed. The initUI method retrieves the new placeholder instance while the WebView instance is still the same because the webView class field is not changed, so the WebView is not created again and all we have to do is attach the old WebView to the new webViewPlaceholder instance.

This implementation is very useful in case you defined a different layout depending on the screen orientation. For example, you could have a my_activity_layout.xml file in the res/layout folder of your Android project for the portrait orientation and another my_activity_layout.xml file in the res/layout-land folder with a different content for the landscape orientation. When onConfigurationChanged is called, the system already has the appropriate resources for the new configuration and when you call the setContentView method passing it a layout resource id, the system uses the correct layout file for the current screen orientation, you just have to make sure that the ids of the UI elements are the same in both files and that the webViewPlaceholder element is present in both the layout configurations. This is what I have also in the internal web browser of my FWebLauncher app and that allows me to have a different position and layout of the buttons bar depending on the screen orientation while keeping the same WebView instance.

I’m sure you noticed that the onSaveInstanceState and onRestoreInstanceState methods are still there, but why since the activity is not destroyed anymore? Well, they are there in case the activity is destroyed for a reason that is not a configuration change (e.g. the system needs to destroy the activity because it is not in foreground and there’s a need to free some memory). In that case we can still restore the original state of the WebView, just without preserving the complete state because we’re going to lose the zoom and the scroll position for example, anyway it’s still better than totally losing the WebView state.

If you want to read more about the configuration change handling in Android, you can check the specific page of the Android documentation. You can also download an example application through the link on top of this post to take a look at the source code that shows what I described in the post.

Leave a comment