Fred Fred - 2 months ago 17
Android Question

Handling screen rotation in WebView

My web app works great in Chrome which handles configuration changes (such as screen rotation) excellent. Everything is perfectly preserved.

When loading my web app into a

in my Android app then the web app loses state on screen orientation change. It does partially preserve the state, i.e. it preserves the data of the
<input>
form elements, however all JavaScript variables and DOM manipulation gets lost.

I would like my WebView to behave the way Chrome does, i.e. fully preserving the state including any JavaScript variables. It should be noted that while Chrome and WebView derives from the same code base Chrome does not internally use WebView.

What happens on screen orientation change is that the Activity (and any eventual Fragments) gets destroyed then subsequently recreated.
WebView
inherits from
View
and overrides the methods
onSaveInstanceState
and
onRestoreInstanceState
for handling configuration changes hence it automatically saves and restores the contents of any HTML form elements as well as the back/forward navigation history state. However the state of the JavaScript variables and the DOM is not saved and restored.

Proposed solutions



There have been a few proposed solutions. All of them non-working, only preserving partial state or in other ways suboptimal.

Assigning the WebView an id



WebView
inherits from
View
which had the method
setId
which can also be declared in the layout XML file using the
android:id
attribute in the declaration of the
<WebView>
element. This is necessary for the state to be saved and restored, however the state is only partially restored. This restores form input elements but not JavaScript variables and the state of the DOM.

onRetainNonConfigurationInstance and getLastNonConfigurationInstance



onRetainNonConfigurationInstance
and
getLastNonConfigurationInstance
are deprecated since API level 13.

Forcing screen orientation



An Activity can have its screen orientation forced by setting the
screenOrientation
attribute for the
<Activity>
element in the
AndroidManifest.xml
file or via the
setRequestedOrientation
method. This is undesired as it breaks the expectation of screen rotation. This also only deals with the change of screen orientation and not other configuration changes.

Retaining the fragment instance



Does not work. Calling the method
setRetainInstance
on a fragment does retain the fragment (it does not get destroyed), hence all the instance variables of the fragment are preserved, however it does destroy the fragment's view hence the
WebView
does gets destroyed.

Manually handling configuration changes



The
configChanges
attribute can be declared for an
Activity
in the
AndroidManifest.xml
file as
android:configChanges="orientation|screenSize"
to handle configuration changes by preventing them. This works, it prevents the activity from getting destroyed hence the
WebView
and its contents is fully preserved. However this has been discouraged and is said to be used only as a last resort solution as it may cause the app to break in subtle ways and get buggy. The method
onConfigurationChanged
gets called when the
configChanges
attribute is set.

MutableContextWrapper



I heard
MutableContextWrapper
can be used, but I haven't evaluated this approach.

saveState() and restoreState()



WebView
have the methods
saveState
and
restoreState
. Note accoriding to the documentation the
saveState
method no longer stores the display data for the WebView whatever that means. Either way these methods do not seem to fully preserve the state of the WebView.

WebViewFragment



The
WebViewFragment
is just a convenience fragment that wraps
WebView
for you so can easily get going with less boilerplate code, much like the
ListFragment
. It does not do any additional state preserving to fully preserve the state.

Question



Is there any real solution to the problem of
WebView
getting destroyed and losing its state upon configuration changes? (such as screen rotation)

A solution that fully preserves all the state including JavaScript variables and DOM manipulation. A solution that is clean and not built on hacks or deprecated methods.

Answer

After researching and trying out different approaches I have discovered what I have come to believe is the optimal solution.

It uses setRetainInstance to retain the fragment instance along with addView and removeView in the onCreateView and onDestroyView methods to prevent the WebView from getting destroyed.

MainActivity.java

public class MainActivity extends Activity {
    private static final String TAG_FRAGMENT = "webView";

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

        WebViewFragment fragment = (WebViewFragment) getFragmentManager().findFragmentByTag(TAG_FRAGMENT);
        if (fragment == null) {
            fragment = new WebViewFragment();
        }

        getFragmentManager().beginTransaction().replace(android.R.id.content, fragment, TAG_FRAGMENT).commit();
    }
}

WebViewFragment.java

public class WebViewFragment extends Fragment {
    private WebView mWebView;

    public WebViewFragment() {
        setRetainInstance(true);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.fragment_webview, container, false);
        LinearLayout layout = (LinearLayout)v.findViewById(R.id.linearLayout);
        if (mWebView == null) {
            mWebView = new WebView(getActivity());
            setupWebView();
        }
        layout.removeAllViews();
        layout.addView(mWebView, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));

        return v;
    }

    @Override
    public void onDestroyView() {
        if (getRetainInstance() && mWebView.getParent() instanceof ViewGroup) {
            ((ViewGroup) mWebView.getParent()).removeView(mWebView);
        }
        super.onDestroyView();
    }

    private void setupWebView() {
        mWebView.loadUrl("https:///www.example.com/");
    }
}
Comments