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"/>
Related
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.
As a beginner android developer I have faced with the problem concerning the fragments and activities handling when screen orientation is changed.
This is my situation:
I have two fragments (F_A and F_B). F_A contains ListView (LV), and F_B contains combination of several Views: Description of LV (lets call it as Desc).
When app runs in portrait mode first activity shows LV and after clicking on an item the second activity runs. In this case when the orientation of the screen is changed from portrait to landscape mode I want to change the layout to show both fragments as it is done when the app initially runs in landscape mode.
I hope the provided pieces of codes will not confuse my description of the situation more :)
first activity
main_activity.java file contains:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); // there is two actitivy_main.xml files
// one in layout/ directory and the other one in layout-land/ directory.
...
}
#Override
public void respond (int index) {
// this method got the second fragment if it is visible and not null.
// otherwise starts new activity for only description fragment (F_B)
}
second activity (for portrait mode only)
DescriptionActivity.java file contains:
#Override
protected void onSaveInstanceState(Bundle outState) {
// saving some stuff for further handling when orientation will changed from port to land.
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_description); // This layout contains only F_B fragment
... //here some initialization is done for the description.
}
Considering that during the screen rotation the Life Cycle guard methods such as onPause(), onStart() etc. are called I think that I have to check the orientation in that methods then destroy the second activity and run the first: main_activity which will itself check what layout should be shown (in this case the layout from layout-land/ directory, I am not sure weather this is a good idea, so I think to ask for help to developers with more experience.
Many thanks in advance,
Arsen
The best way to achieve what you want is described in Fragments Android documentation, there you have an example with the full explanation.
But just as an advice, you don't have to use 2 diff activities nor check the orientation of the device, you just have to create an activity_main.xml for diff configurations in layout-port (portrait layout) and layout-land (landscape layout) you can learn how in here: Supporting Different Screens and Orientations. I know it's kind of hard at the beginning but working with fragments is one of the best practices for android. Read those tutorials and you'll be able to achieve your goal. Good luck :)
In My application i am setting orientation of the application on button click, using setRequestedOrientation(), My application has one webview which displays local web page.
I am using solution as mentioned in below link, to stop reloading of page when orientation changes.
http://www.devahead.com/blog/2012/01/preserving-the-state-of-an-android-webview-on-screen-orientation-change/
Only difference is that i am not having onSaveInstanceState and onRestoreInstanceState in my activity.
What happens in my case is that, my web view does not reload when i change the orientation but it just re-renders the UI upon changing the orientation.
It happens like it first displays the UI and then it displays the white blank screen for few seconds and then again it displays UI.
How can i workaround it?
Once upon a time I used their implementation however I after reading up on the Android documentation I realized that much of their code was pointless since registering for config changes (in the manifest) means that your activity won't be destroyed as it normally is.
Long story short, you don't need to use a FrameLayout as a container for your webView, you may not need onConfigurationChanged, etc. If I get some time I will post some code as an example.
For now, make sure you have the following in your manifest file
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
instead of
android:configChanges="keyboard|keyboardHidden|orientation"
since on orientation change devices may get get a screenSize change
EDIT This is what I have done (skimmed from one of my projects so I may have missed minor details)
in AndroidManifest.xml
<activity android:name=".Activities.WebWrapperActivity"
android:theme="#android:style/Theme.NoTitleBar"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"/>
web_wrapper_activity_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<WebView android:id="#+id/web_wrapper_activity_web_view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:scrollbarStyle="outsideOverlay"/>
</LinearLayout>
The Activity (or a portion of it)
public class WebWrapperActivity extends Activity {
private WebView _webView;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.web_wrapper_activity_layout);
init();
}
private void init() {
_webView = (WebView) findViewById(R.id.web_wrapper_activity_web_view);
_webView.setScrollbarFadingEnabled(true);
_webView.getSettings().setLoadsImagesAutomatically(true);
_webView.getSettings().setJavaScriptEnabled(true);
_webView.getSettings().setAllowFileAccess(true);
_webView.getSettings().setSavePassword(false);
_webView.getSettings().setPluginState(WebSettings.PluginState.ON);
_webView.loadUrl(“http://www.google.com”);
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
_webView.saveState(outState);
}
}
As i mentioned previously, you may not need onConfigurationChanged if your webView is the only view in the Activity.
If you have any issues let me know
if the question sounds weird at first, here comes the explanation:
I have got an activity that hosts my three fragments. Since I would like one of my fragments to save its instance state when the device is rotated, I defined this in my manifest for my activity that hosts the fragments:
android:configChanges="orientation|screenSize"
This works just fine. However, now I have got an other problem: One of my other fragments uses a special landscape layout. The problem is, that this layout is not used immediately on device rotation. I think it is because the new layout only gets set on onCreate.
What can I do to solve this problem? I want my landscape layout to be set immediately.
You can put
setRetainInstance(true);
in onCreateView(); method of your Fragment. I think it should do the trick.
As far as I know you down need to add the configChanges parameter to your manifest.
You can override onSaveInstanceState() in your Fragment
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
savedInstanceState.putInt(KEY_INDEX, someIntValue);
}
This methode should be called before your fragment gets destroyed.
Now in your onCreate(Bundle savedInstanceState) (or onCreateView()) methode:
if (savedInstanceState != null) {
someIntValue = savedInstanceState.getInt(KEY_INDEX);
}
This way it shouldn't intervene with any other special fragments.
I have seen the following links before posting this question
http://www.devx.com/wireless/Article/40792/1954
Saving Android Activity state using Save Instance State
http://www.gitshah.com/2011/03/how-to-handle-screen-orientation_28.html
How to save state during orientation change in Android if the state is made of my classes?
I am not getting how should i override the following function :
#Override
public Object onRetainNonConfigurationInstance() {
return someExpensiveObject;
}
In my application i have layout with one editext visible and other editext get visible when the data of first editext validates to true.I have set the visbility of all other editextes and textviews to false and make them visible after validating.
So in my activity if the screen orientation is changed then all the items having android:visibility="false" get invisible.
I have also came to know that when our activities screen orientation changes it calls onStop() followed by onDestroy() and then again starts a fresh activity by calling onCreate()
This is the cause .. But i am not getting how to resolve it ..
Here You can see the screenshots of my application :
in this image all fields are loaded
and in another image when the screen orientation is changed to landscape they are all gone
Any link to tutorial or piece of code will be highly appreciable.
And also my application crashes when a progress dialog is shown up and i try to change screen orientation.How to handle this ??
Thanks
Well if you have the same layout for both screens then there is no need to do so just add below line in your manifest in Activity node
android:configChanges="keyboardHidden|orientation"
for Android 3.2 (API level 13) and newer:
android:configChanges="keyboardHidden|orientation|screenSize"
because the "screen size" also changes when the device switches between portrait and landscape orientation.
From documentation here: http://developer.android.com/guide/topics/manifest/activity-element.html
There is another possibility using which you can keep the state as it is even on Orientation change using the onConfigurationChanged(Configuration newConfig).
Called by the system when the device configuration changes while your activity is running. Note that this will only be called if you have selected configurations you would like to handle with the configChanges attribute in your manifest. If any configuration change occurs that is not selected to be reported by that attribute, then instead of reporting it the system will stop and restart the activity (to have it launched with the new configuration).
At the time that this function has been called, your Resources object will have been updated to return resource values matching the new configuration.
There are 2 ways of doing this, the first one is in the AndroidManifest.xml file. You can add this to your activity's tag. This documentation will give you an in depth explanation, but put simply it uses these values and tells the activity not to restart when one of these values changes.
android:configChanges="keyboardHidden|orientation|screenSize|screenLayout"
And the second one is: overriding onSaveInstanceState and onRestoreInstanceState. This method requires some more effort, but arguably is better. onSaveInstanceState saves the values set (manually by the developer) from the activity before it's killed, and onRestoreInstanceState restores that information after onStart() Refer to the official documentation for a more in depth look. You don't have to implement onRestoreInstanceState, but that would involve sticking that code in onCreate().
In my sample code below, I am saving 2 int values, the current position of the spinner as well as a radio button.
#Override
public void onSaveInstanceState(#NonNull Bundle savedInstanceState) {
spinPosition = options.getSelectedItemPosition();
savedInstanceState.putInt(Constants.KEY, spinPosition);
savedInstanceState.putInt(Constants.KEY_RADIO, radioPosition);
super.onSaveInstanceState(savedInstanceState);
}
// And we restore those values with `getInt`, then we can pass those stored values into the spinner and radio button group, for example, to select the same values that we saved earlier.
#Override
public void onRestoreInstanceState(#NotNull Bundle savedInstanceState) {
spinPosition = savedInstanceState.getInt(Constants.KEY);
radioPosition = savedInstanceState.getInt(Constants.KEY_RADIO);
options.setSelection(spinPosition, true);
type.check(radioPosition);
super.onRestoreInstanceState(savedInstanceState);
}