Fully retain WebView in fragment after rotation - android

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?

Related

WebView in Fragment in ActionBar-navigated Activity - how to avoid reload?

So I have an activty with 3 tabs which I navigate between through the ActionBar (SupportActionBar). Each of the tabs has a Fragment with a WebView attached to them, with a TabListener implement as examplified in the Android Docs.
This all works fine, except that the onCreateView method of the Fragment is called each time a fragment is reattached. This in turn causes the WebView to either (1) be blank or (2) reload, if I call restoreState() on it (which I previously have saved manually).
I don't want the page to reload each time the user switches tabs. Neither do I want the scrollbar to reset. Or the HTML forms (if any) to be reset. How can I accomplish this?
Solved by keeping a reference to the WebView in the fragment class, and in onCreateView I do this:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if (mWebView == null) {
mWebView = new WebView(container.getContext());
mWebView.restoreState(savedInstanceState);
}
else {
View parent = mWebView.getParent();
if (parent != null && parent instanceof ViewGroup)
((ViewGroup) parent).removeView(mWebView);
}
return mWebView;
}

WebView prevent reloading in RetainInstance fragment

I am having a problem preventing a WebView from reloading when inside a fragment with retainInstance set to true.
The routes I have tried:
SaveInstanceState (Doesn't pass a bundle on rotation because of retainInstance)
A custom bundle which is saved on onSaveInstanceState and restored in create
Keeping the WebView reference and attempt to add it back to the view hierarchy
There must be an easier way?
You can do this pretty easily. Since you're already retaining the instance, keep a reference to the WebView in the Fragment and detach it from the parent when the Fragment's onDestroyView() is called. If it's non-null, just return that in onCreateView(). For example:
public class RetainedWebviewFragment extends Fragment {
private WebView mWebView;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if (mWebView == null) {
mWebView = new WebView(getActivity());
}
return mWebView;
}
#Override
public void onDestroyView() {
super.onDestroyView();
if (getRetainInstance() && mWebView.getParent() instanceof ViewGroup) {
((ViewGroup) mWebView.getParent()).removeView(mWebView);
}
}
}
However, you will leak one reference to the first Activity on the first orientation change -- something I've not yet figured out how to avoid. tThere's no way to set the context on a View to my knowledge. This does work, though, if you're okay with that one memory leak.
EDIT: Alternatively, if you're instantiating it programmatically as I'm doing in this example, you could just use getActivity().getApplicationContext() to avoid leaking the activity. If you use the provided LayoutInflater, it will give the application's context to the Views when inflating.
Don't use the application context for a WebView -- this will cause unexpected crashes when showing dropdown views and potentially other problems.

How to prevent the reload of a webView in a fragment when another fragment in the top was closed

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.

Stop fragment from being recreated after resume?

I am using several fragments to be dynamically added into activity. Everything works fine, when I press back-button, the fragments go to backstack. And when I resume it, it appears. But everytime on Resume, it is recreating the fragment and call onCreateView. I know it is a normal behavior of the fragment lifecycle.
This is my onCreateView:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(
R.layout.competitive_programming_exercise, container, false);
return rootView;
}
I want to stop those fragments from recreating. I tried with onSavedInstanstate but nothing is working. How can I accomplish that?
In the Activity's onCreateView set the savedInstanceState to null before calling the super method. You could also remove only the keys "android:viewHierarchyState" and "android:fragments" from the savedInstanceState bundle. Here is code for the simple solution, nulling the state:
#Override
public void onCreate(Bundle savedInstanceState)
{
savedInstanceState = null;
super.onCreate(savedInstanceState);
...
}
Iam using 5 fragments and working for me good as I was facing the same issue before..
public class MyFragmentView1 extends Fragment {
View v;
#Override
public View onCreateView(LayoutInflater inflater,
#Nullable ViewGroup container,
#Nullable Bundle savedInstanceState) {
if (v == null)
v = inflater.inflate(R.layout.my_fragment_view_layout,
container, false
);
return v;
}
}
I put the view variable inside class and inflating it as new only if the view instance is null or otherwise use the one created before
You can't stop the fragment from being recreated, unfortunately. The best you can do is to remove the fragment in a transaction, after it has been restored but before it gets displayed.
If you know you are going to remove the fragment immediately you can reduce the performance hit of restoring the fragment by simplifying methods such as onCreateView() to return a dummy view, rather than inflating the whole view hierarchy again.
Unfortunately the tricky part is finding the best place to commit this transaction. According to this article there are not many safe places. Perhaps you can try inside FragmentActivity.onResumeFragments() or possibly Fragment.onResume().

Handling orientation changes with Fragments

I'm currently testing my app with a multipane Fragment-ised view using the HC compatibility package, and having a lot of difficultly handling orientation changes.
My Host activity has 2 panes in landscape (menuFrame and contentFrame), and only menuFrame in portrait, to which appropriate fragments are loaded. If I have something in both panes, but then change the orientation to portrait I get a NPE as it tries to load views in the fragment which would be in the (non-existent) contentFrame. Using the setRetainState() method in the content fragment didn't work. How can I sort this out to prevent the system loading a fragment that won't be shown?
Many thanks!
It seems that the onCreateViewMethod was causing issues; it must return null if the container is null:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
if (container == null) // must put this in
return null;
return inflater.inflate(R.layout.<layout>, container, false);
}
Probably not the ideal answer but if you have contentFrame for portrait and in your activity only load up the menuFrame when the savedInstanceState is null then your content frame fragments will be shown on an orientation change.
Not ideal though as then if you hit the back button (as many times as necessary) then you'll never see the menu fragment as it wasn't loaded into contentFrame.
It is a shame that the FragmentLayout API demos doesn't preserve the right fragment state across an orientation change. Regardless, having thought about this problem a fair bit, and tried out various things, I'm not sure that there is a straightforward answer. The best answer that I have come up with so far (not tested) is to have the same layout in portrait and landscape but hide the menuFrame when there is something in the detailsFrame. Similarly show it, and hide frameLayout when the latter is empty.
Create new Instance only for First Time.
This does the trick:
Create a new Instance of Fragment when the
activity start for the first time else reuse the old fragment.
How can you do this?
FragmentManager is the key
Here is the code snippet:
if(savedInstanceState==null) {
userFragment = UserNameFragment.newInstance();
fragmentManager.beginTransaction().add(R.id.profile, userFragment, "TAG").commit();
}
else {
userFragment = fragmentManager.findFragmentByTag("TAG");
}
Save data on the fragment side
If your fragment has EditText, TextViews or any other class variables
which you want to save while orientation change. Save it
onSaveInstanceState() and Retrieve them in onCreateView() method
Here is the code snippet:
// Saving State
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("USER_NAME", username.getText().toString());
outState.putString("PASSWORD", password.getText().toString());
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.user_name_fragment, parent, false);
username = (EditText) view.findViewById(R.id.username);
password = (EditText) view.findViewById(R.id.password);
// Retriving value
if (savedInstanceState != null) {
username.setText(savedInstanceState.getString("USER_NAME"));
password.setText(savedInstanceState.getString("PASSWORD"));
}
return view;
}
You can see the full working code HERE

Categories

Resources