Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Prevent WebView fragment from reloading page on screen rotation

I know this has been asked too many times and that SO is full of similar questions. I went through most of them and I've been researching this issue for a couple of days and I have yet to find the definitive solution.

I could easily avoid all this trouble adding configChanges="orientation|screenSize" to the activity containing the WebView; I've tested this and it worked as intended (the WebView didn't reload). But I really wanted to avoid this solution.

Here's my current WebView implementation:

public class BrowserFragment extends Fragment {

    private WebView mWebView;

    public BrowserFragment() {
        // Required empty public constructor
    }

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

        setRetainInstance(true);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        if (mWebView == null) {
            mWebView = new WebView(getActivity());
        }

        return mWebView;
    }

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

        mWebView.setWebViewClient(new WebViewClient() {

            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                return super.shouldOverrideUrlLoading(view, url);
            }

        });

        mWebView.getSettings().setAllowFileAccess(true);
        mWebView.getSettings().setBuiltInZoomControls(false);
        mWebView.getSettings().setDomStorageEnabled(true);
        mWebView.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
        mWebView.getSettings().setJavaScriptEnabled(true);
        mWebView.getSettings().setSupportZoom(false);

        if (savedInstanceState == null) {
            mWebView.loadUrl("http://developer.android.com/");
        } else {
            mWebView.restoreState(savedInstanceState);
        }
    }

    @Override
    public void onResume() {
        mWebView.onResume();

        super.onResume();
    }

    @Override
    public void onPause() {
        super.onPause();

        mWebView.onPause();
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);

        mWebView.saveState(outState);
    }

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

        super.onDestroyView();
    }

}

As for the MainActivity, there's nothing there besides setting the content view to the following layout file:

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
       android:layout_width="match_parent"
       android:layout_height="match_parent">

    <fragment
        android:id="@+id/fragment_browser"
        android:name="com.example.app.BrowserFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</merge>

Notice above that I'm retaining the fragment instance and I'm also saving the state of the WebView so I can late restore that same state. This is what the documentation has to say about the restoreState method:

Restores the state of this WebView from the given Bundle. This method is intended for use in onRestoreInstanceState(Bundle) and should be called to restore the state of this WebView. If it is called after this WebView has had a chance to build state (load pages, create a back/forward list, etc.) there may be undesirable side-effects. Please note that this method no longer restores the display data for this WebView.

Meaning, the display data will not be restored but things like the scroll position will (I also tested this and it's working nicely). That's why I am creating a new instance of the WebView, only if it's null during onCreateView and removing it from the parent view during onDestroyView. This was taken from this answer: https://stackoverflow.com/a/32801278/40480

This mechanism will make the rotate process a little bit more smooth (without it, a full blank page was shown) but won't prevent the WebView from still reloading the page.

Any ideas how can I possibly solve this problem?

like image 860
rfgamaral Avatar asked Apr 22 '16 18:04

rfgamaral


1 Answers

I had a similar problem. It's a bit late but my workaround is similar than yours but adding the webview again to the layout in onCreateView:

  • create new WebView if instance is null (first time) and add it to layout (in my case parent is a FrameLayout, at position 0).
  • if already created (activity recreated) just add to frame and invalidate (important to redraw webview on rotation)

    mFrame = (FrameLayout) mView.findViewById(R.id.dq_fragment_browser_common_frame);
    
    if (mWebView == null){
        mWebView = new CommonWebView(getContext());
        mFrame.addView(mWebView,0);
    } else {
        mFrame.addView(mWebView,0);
        mWebView.invalidate();
    }
    

As in your case I remove webview from frame in OnDestroyView() to be able to add it again in onCreateView:

@Override
public void onDestroyView() {
    mFrame.removeView(mWebView);
    super.onDestroyView();
}

NOTE: I don't save and store the webview state as it reloads the page (at least in my case). Furthermore, the scroll position does not normally match in portrait/landscape but same problem in Chrome as far as I've checked.

Regards.

like image 51
Dmt Avatar answered Nov 11 '22 06:11

Dmt