Hello I've a problem when using my app on the phone, when I rotate the screen inside the app the webview reloads all content getting back to the first page we were opening the app.
I leave you a gif of what happens here and if you know a solution please help me. I also leave my main class and the android manifest.
Proof Video
Main.java
AndroidManifest.xml
Add android:configChanges="orientation|screenSize" in your Manifest.xml file
Like below:
<activity
android:name=".MyActivity"
android:configChanges="orientation|screenSize"
android:label="#string/title_activity"
android:theme="#style/AppTheme.MyTheme">
</activity>
Create a fragment to wrap your WebView up. Always return the same WebView in onCreateView, so that a new WebView would not be created on orientation change, thus not reloading.
public class MyWebViewFragment extends Fragment {
private WebView mWebView;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
#Override
public void onResume() {
super.onResume();
mWebView.onResume();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if (mWebView == null)
mWebView = new WebView(getActivity());
ViewGroup parent = (ViewGroup) mWebView.getParent();
if (parent != null)
parent.removeView(mWebView);
return mWebView;
}
}
Add this fragment to your activity.
Related
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?
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.
I'm making an app with webviews.
I just have a single activity containing
a FrameLayout to dynamically add fragments.
Every fragment contains a webView.
Everything works fine, but when I remove a fragment
from the stack, the webview of the fragment in the
top of the stack is reloaded so the content inside the
script of the html is called again.
The restoreState() method is not working.
This is the onCreateView of the fragment:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
view = inflater.inflate(R.layout.wv_container, container, false);
webView = (WebView)view.findViewById(R.id.webviewcontainer);
if(webViewBundle == null)
webView.loadUrl("file:///android_asset/www/main.html");
else
webView.restoreState(webViewBundle);
return view;
}
This is the onPause() method:
#Override
public void onPause() {
super.onPause();
webViewBundle = new Bundle();
webViewContent.saveState(webViewBundle);
}
How should I prevent the page reloading for my case?
I don't want to hack the html with flags or something like that.
Thanks in advance!
The saveState method in your onPause returns a WebBackForwardList, which contains a list of WebHistory items. These are basically the titles, urls and favicons from recently visited pages. It does not cache the contents of the downloaded url.
Try looking at the method saveWebArchive instead.
I have tried a every solution I can find and am not having any luck. I have multiple dynamic textViews that change not only text but formatting such as alpha, strike-through... I can not find a reliable way to save this information through screen rotation. I am self taught and new my head is spinning...
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
getFragmentManager().beginTransaction()
.add(R.id.container, new UIrFragment())
.commit();
}
}
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_main, container, false);
setRetainInstance(true);
Log.i(Tag, "onCreateView");
return rootView;
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
ImageView background = (ImageView) getView().findViewById(R.id.background);
background.setImageResource(R.drawable.starrynightblurry);
setupKeyBoard(); // multiple textViews get set up here. Want to save this
setupGame();// same here
}
Apparently when using setRetainInstance(true), the Bundle for onRestoreInstanceState is null.
store text views in
#Override
public void onSaveInstanceState(Bundle outState)
{
super.onSaveInstanceState(outState);
//store here
}
Restore text view from here
#Override
public void onRestoreInstanceState(Bundle savedInstanceState)
{
super.onRestoreInstanceState(savedInstanceState);
// restore here
}
hope this helps you
Add below line in activity_main.xml
<activity android:name=".MainActivity"
android:configChanges="keyboardHidden|orientation" >
either fix screen orientation to either landscape or portrait in your AndroidManifest.xml like this:
android:screenOrientation="portrait"
or handle the configChanges like this:
android:configChanges="orientation|keyboardHidden|smallestScreenSize|uiMode"
should be added as an attribute of the <activity> tag in the manifest. The configChanges is used to specify configuration changes that the activity will handle itself.
Edit: for handling config changes in Fragments, please see this question.
I'm currently having a WebView placed in a fragment like this:
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
mContext = container.getContext();
LayoutInflater mInflater = (LayoutInflater)mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
LinearLayout view = (LinearLayout) mInflater.inflate(R.layout.post_view_layout, container,false);
view.setBackgroundColor(Color.WHITE);
_viewCache = view;
_post_WebView = (WebView) view.findViewById(R.id.post_webview);
setupWebView(_post_WebView); //This method sets up the webview (javascript mode etc.) and set the url to load
return view;
}
Now whenever I rotate, the activity that contains this fragment gets recreated (I can't use the configChanges="orientation" attribute for the manifest because I'm using ABS)
Because of setRetainInstance(true) the fragment, it's layout and all variables get retained fine and all, but whenever I try to scroll or click in the retained WebView I get a BadTokenException (probably because the original activity used for it's context is destroyed during rotation).
I could solve this by just recreating the WebView with the new activity context after rotation, but since the webview shows a input form, recreating it after rotation might prove tedious to users. (I already tried saving the state using the saveState and restoreState methods of the WebView, but to no avail)
Is there any way to restore or retain the WebView in it's complete pre-rotation state without generating a BadTokenException, just like it would when the configChanges="orientation" attribute has been added?
I have a simple layout containing a VideoView.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#color/black"
android:gravity="center" >
<VideoView
android:id="#+id/videoPlayer"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>
</RelativeLayout>
The Activity that uses this layout creates a Fragment to start the VideoView
public class VideoPlayerActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_video_player);
createNewWorkerFragment();
}
private void createNewWorkerFragment() {
FragmentManager fragmentManager = getFragmentManager();
VideoPlayerActivityWorkerFragment workerFragment = (VideoPlayerActivityWorkerFragment)fragmentManager.findFragmentByTag(VideoPlayerActivityWorkerFragment.Name);
if (workerFragment == null) {
workerFragment = new VideoPlayerActivityWorkerFragment();
fragmentManager.beginTransaction()
.add(workerFragment, VideoPlayerActivityWorkerFragment.Name)
.commit();
}
}
}
The VideoPlayerActivityWorkerFragment is as follows:
public class VideoPlayerActivityWorkerFragment extends Fragment {
public static String Name = "VideoPlayerActivityWorker";
private VideoView mVideoPlayer;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
mVideoPlayer = (VideoView) mActivity.findViewById(R.id.videoPlayer);
mVideoPlayer.setVideoPath(mActivity.getIntent().getExtras().getString("path"));
MediaController controller = new MediaController(mActivity);
controller.setAnchorView(mVideoPlayer);
mVideoPlayer.setMediaController(controller);
mVideoPlayer.requestFocus();
mVideoPlayer.start();
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
mActivity = activity;
}
#Override
public void onDetach() {
super.onDetach();
mActivity = null;
}
}
This is the issue I'm having, when the VideoPlayerActivity starts the VideoPlayerActivityWorkerFragment is created and the VideoView starts playing, however when I rotate the device the video stops and will not play, the entire View seems gone from the layout. Due to setRetainInstance(true); I thought that the VideoView would continue to play. Can someone let me know what I'm doing wrong? I have used this pattern elsewhere (not with a VideoView) and it successfully allows rotation to happen.
I am unwilling to use the android:configChanges="keyboardHidden|orientation|screenSize" or similar methods, I would like to handle the orientation change with Fragments.
Thank you in advance for your help.
Edit
I ended up picking the solution I did because it works. The videoview and controller need to be recreated each time onCreateView is called and the playback position needs to be set in onResume and recored in onPause. However, the playback is choppy during the rotation. The solution is not optimal but it does work.
See mVideoPlayer = (VideoView) mActivity.findViewById(R.id.videoPlayer);
Your VideoView is created by the activity which is being destroyed and recreated on rotation. This means a new R.id.videoPlayer VideoView is being created. So your local mVideoPlayer is just being overwritten in your above line. Your fragment needs to create the VideoView in its onCreateView() method. Even then, this may not suffice. Because Views are inherently linked with their owning Context. Perhaps an explicit pause, detect and attach, play of the view would be a better way to go.
Here are some observations:
1.First of all, you should know that even if a fragment is retained the activity gets distroyed.
As a result, the onCreate() method of activity, where you add the fragment, is called every time thus resulting in multple fragments overlaping each other.
In VideoPlayerActivity you need a mechanism to determine whether the activity is created for the first time, or is re-created due to a configuration change.
You can solve this problem by putting a flag in onSaveInstanceState() and then checking the savedInstanceState in onCreate(). If it's null, it means the activity is created for the first time, so only now you can add the fragment.
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
if (savedInstanceState == null) {
createNewWorkerFragment();
}
}
// ....
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean("fragment_added", true);
}
2.Secondly, in VideoPlayerActivityWorkerFragment you are refering to the VideoView declared in the activity:
mVideoPlayer = (VideoView) mActivity.findViewById(R.id.videoPlayer);
which gets destroyed when the screen is rotated.
What you should do, is to extract that VideoView and put it in a separate layout file that will be inflated by the fragment in the onCreateView() method.
But even here you may have some troubles. Despide the fact that setRetainInstance(true) is called, onCreateView() is called every time on screen orientation, thus resulting in re-inflation of your layout.
The solution would be to inflate the layout in onCreateView() only once:
public class VideoPlayerActivityWorkerFragment extends Fragment {
private View view;
//.....
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if (view == null) {
view = inflater.inflate(R.layout.my_video_view, container, false);
mVideoPlayer = (VideoView) view.findViewById(R.id.videoPlayer);
// .....
} else {
// If we are returning from a configuration change:
// "view" is still attached to the previous view hierarchy
// so we need to remove it and re-attach it to the current one
ViewGroup parent = (ViewGroup) view.getParent();
parent.removeView(view);
}
return view;
}
}
}
If all the above said does not make sense for you, reserve some time to play with fragments, especially inflation of a layout in a fragment, then come back and read this answer again.