Android webView saveState - android

I have a tabhost with 3 tabs. In each of these tabs there is a webiview.
When i click a tab the webview need to "reload" even when i have been there before, it have not been saved.
Is there any way to save the webview ?

This can be handled by overrwriting onSaveInstanceState(Bundle outState) in your activity and calling saveState from the webview:
#Override
protected void onSaveInstanceState(Bundle outState) {
webView.saveState(outState);
super.onSaveInstanceState(outState);
}
Then recover this in your onCreate after the webview has been re-inflated of course:
#Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.blah);
WebView webview = (WebView)findViewById(R.id.webview);
if (savedInstanceState != null)
webview.restoreState(savedInstanceState);
else
webview.loadUrl(URLData)
}

restoreState was never reliable. And come 2015 the documents accept the fact. (The change was probably made to the documents around 2014).
This is what it says now.
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.
And the corresponding entry for saveState() speaks thus:
Please note that this method no longer stores the display data for
this WebView.
What you really should do inside the onCreate method is to call webView.loadUrl() if you want to display the last visited url, please see this answer:
If you are concerned about the webview reloading on orientation change etc.
you can set your Activity to handle the orientation and keyboardHidden changes, and then just leave the WebView alone

Alternatively you can look into using fragments with your tabHost. when entering another webView you can set fragment to hide and the other to show. when you return to the previous fragment the content will not have been destroyed. Thats how i did it and it worked for me.

Related

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 WebView 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 according 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.
This class was deprecated in API level 28.
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.
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/");
}
}
I would suggest you re-render the whole thing again. I searched a while and I couldn't find a clean ready made solution. Everything out there states that you let the webpage re-render on orientation change.
But if you really need to persist your JS variables, you could imitate what saveState and restoreState did. What these methods ideally do is save and restore stuff in WebView using the activity's onSaveInstanceState() and onRestoreInstanceState() respectively. These methods don't do the stuff they did because of potential memory leaks.
So, all you need to do is create your own webview (MyWebView extends WebView). In this have two methods: saveVariableState() and restoreVariableState(). In your saveVariableState() just save every variable you want in a bundle and return it ( public Bundle saveVariableState(){}). Now in the onSaveInstanceState() of the Activity, call MyWebView.saveVariableState and save the bundle it returns. Once the orientation changes, you fetch the bundle from onRestoreInstanceState or onCreate and pass it to the MyWebView via the constructor or restoreVariableState.
This is not a hack, but the normal way to save stuff of data's of other views. In case of WebView, instead of saving data of the view, you are going to save JS variables.
the following manifest code declares an activity that handles both the screen orientation change and keyboard availability change:
this code:
<activity android:name=".MyActivity">
becomes :
<activity android:name=".MyActivity"
android:configChanges="orientation|screenSize|keyboardHidden"
android:label="#string/app_name">
here you only add: screenSize, then it works fine.
reference: https://developer.android.com/guide/topics/resources/runtime-changes.html#RetainingAnObject
Simple thing you could do is something like:
WebView webView;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_webview, container, false);
webView = v.findViewById(R.id.webView);
if(savedInstanceState != null){
webView.restoreState(savedInstanceState);
} else {
loadUrl();
}
return v;
}
private void loadUrl(){
webView.loadUrl("someUrlYouWant");
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
webView.saveState(outState);
}
NOTE: Did not try this code just wrote it but think it should work.

How to save WebView state and restore it in Android Lollipop?

This question is asked multiple times and it has answers that used to work. Recently in documentation said, they have removed this feature for security reasons. Only some limited things can be restored for the webView state. I have tried multiple ways to do it but every time it refreshes the webView state and it doesn't show the webView content.
What I want to do?
I have a web app that I want to retrieve the user content after time-out (If they input their credentials correct).
What I have done so far?
1- I have tried all solutions including the following approaches:
#Override
protected void onSaveInstanceState(Bundle outState) {
webView.saveState(outState);
}
#Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.blah);
WebView webview = (WebView)findViewById(R.id.webview);
if (savedInstanceState != null)
webview.restoreState(savedInstanceState);
else
webview.loadUrl(URLData)
}
Also manifest file
android:configChanges="keyboardHidden|orientation|screenSize|screenLayout|uiMode|screenSize|smallestScreenSize"
2- I tried to show my Time-Out activity as a dialog and it worked if I just dismiss it without evaluating the credentials but if I evaluate and dismiss the dialog it recreates the webView.
Any help would be appreciated.
Finally I figured out the solution. There is no way to save the state of WebView and have all of the content, so we need to prevent reloading or recreating the webView. In order to have one instance of our activity that contains webView we should add the following code to our Manifest file:
<activity ...
android:launchMode="singleInstance"
android:alwaysRetainTaskState="true">
Then if through your application you needed to recreate the instance entirely to refresh the webView, you can use finish(); if it's inside of the activity or if you are in another activity and you want to recreate your webView activity, read this link to do it.
Setting android:configChanges="orientation|screenSize" for Activity in Manifest for retaining the state of activity that has Webview in it, if there is no difference in layout for portrait and landscape view.
You can save Webview state as:
override fun onSaveInstanceState(outState: Bundle) { super.onSaveInstanceState(outState) webView.saveState(outState)
}
override fun onRestoreInstanceState(savedInstanceState: Bundle?) { super.onRestoreInstanceState(savedInstanceState) webView.restoreState(savedInstanceState)
}
and reload only if saved state is not null like:
if (savedInstanceState == null) {
webView.loadUrl("replace_with_your_url")
}

Android ViewPager Screen Rotation

I have an Activity with ActionBarSherlock tabs and a ViewPager inside it. When the pages are scrolled, the tabs are switched and tabs are changed, the current page is changed too.
I am using a class that extends FragmentStatePagerAdapter as adapter in the pageview.
The problem is that when the device rotates, the getItem from the page adapter is not called and looks like the fragments references are not right.
That's a huge problem, since the user must fulfill some fields inside the pager. These fields are recovered in correctly on the rotation, but since I the references for the fragments are not right, I can't save these values in right way.
Any idea about the rotation?
Save the current page number in onSavedInstanceState:
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("item", mViewPager.getCurrentItem());
}
Then in onCreate:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(savedInstanceState != null) {
mViewPager.setCurrentItem(savedInstanceState.getInt("item"));
}
}
Nothing solved, created an issue for AOSP:
http://code.google.com/p/android/issues/detail?can=2&start=0&num=100&q=&colspec=ID%20Type%20Status%20Owner%20Summary%20Stars&groupby=&sort=&id=54823
I've posted a solution to this problem in the question here https://stackoverflow.com/a/21517213/3170538. Essentially you want to be subclassing PagerAdapter, not FragmentPagerAdapter, and handling the creating and deleting of items yourself (getItem() is a routine that is called from FragmentPagerAdapter.instantiateItem(), so the issue is when the screen is rotated the routine doesn't re-call getItem(), because of some presumably performance-enhancing code. When you subclass it you can handle the rotating properly by not trying to reconnect to the deleted fragment).

Keep webview while changing layout on screen rotation

My app have two different layout for portrait and landscape mode which both encapsulate a webview and some buttons. Buttons are at bottom in portrait and at left in landscape so more reading space is available in webview.
The problem is, the activity is recreated on screen rotation and webview loads the first page which is not a wanted behavior.
I searched and found out using android:configChanges="orientation" in activity tag prevents recreating of the activity. But the porblem is it prevents the layout changing too as it happens in activity creation.
I want my program to work in 2.2, waht's the best way to this?
I tested fragments, but dealing with fragment makes things much more complex and the fragment itself needs saving and restoring which may not work in a webview which has javascript state, So I searched more and find a nice article somewhere and with some modification I came to a solution which I suggest:
First, add android:configChanges="orientation|screenSize|keyboard|keyboardHidden" to manifest so app handles the config change instead of android.
Make two different layout for lnadscape and portrait mode and put them in corresponding layout folders. In both layouts instead of webview place a LinerLayout which acts as a placeholder for webview.
In code define initUI method like this and put every thing related to UI initialization in this method:
public void initui()
{
setContentView(R.layout.main);
if (wv == null) wv = new WebView(this);
((LinearLayout)findViewById(R.id.webviewplace)).addView(wv);
findViewById(R.id.home).setOnClickListener(this);
}
If the webview doesn't exist, it will be created and after setContentView(R.layout.main) it will be added to the layout. Other UI customization came afterward.
and in onConfigurationChanged:
#Override
public void onConfigurationChanged(Configuration newConfig)
{
((LinearLayout)findViewById(R.id.webviewplace)).removeAllViews();
super.onConfigurationChanged(newConfig);
initUI();
}
In onConfigChange First the webview is removed from old place holder and initui will be called which will add it back to the new layout.
and in oncreate call initui so the ui will be initialized for the first time.
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
initUI()
}
Could you not save the last loaded URL in the webview in onSaveInstanceState(Bundle outState) and then make sure to reload it onRestoreInstanceState(Bundle savedInstanceState) using myWebview.loadUrl(restoredUrl)?
edit I know that this might not work if the web page you are displaying requires a state to be kept. But if not it should be a solution to your problem.
From this document https://developer.android.com/guide/topics/resources/runtime-changes.html#HandlingTheChange
I just edited manifest with android:configchanges so it works fine for me.
<activity android:name=".Webhtml"
android:configChanges="orientation|screenSize"/>

Android muliple WebView save instance

I try to save and restore the state of 3 WebViews in my activity by using onSaveInstanceState() and the restoreState() method of my WebView in onCreate(). This works just fine when I had only one WebView, but with 3 WebViews to save, it looks like it's only the last one save which counts and overrides the other saves; and so when I use the restoreState() method in onCreate(), the first WebView has the content of the third WebView and the 2 others are empty.
Here is a sample of my activity code:
#Override
public void onSaveInstanceState(Bundle outState) {
((WebView)findViewById(R.id.webview1)).saveState(outState);
((WebView)findViewById(R.id.webview2)).saveState(outState);
((WebView)findViewById(R.id.webview3)).saveState(outState);
}
#Override
public void onCreate(Bundle savedInstanceState) {
((WebView)findViewById(R.id.webview1)).restoreState(savedInstanceState);
((WebView)findViewById(R.id.webview2)).restoreState(savedInstanceState);
((WebView)findViewById(R.id.webview3)).restoreState(savedInstanceState);
}
Should I use some trick to use another bundle for each view? Have I done something wrong somewhere else in my code that causes this or is it the standard behaviour?
Your 2nd call of saveState() overwrites the contents saved in the first call and the 3rd call overwrites the content of the 2nd call. At the end of the execution of onSaveInstanceState() the bundle only contains the 3rd WebView's state.
This is why when you restore the state of the 3rd WebView gets restored to the 1st WebView.
Create a new bundle object of each of your WebViews and put them into the outState Bundle object using this giving a different key for each Bundle:
public void putBundle (String key, Bundle value)
In onCreate() get the Bundles based on the 3 keys and restore the states.

Categories

Resources