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.
Related
I could use onCreateView() to get a reference to a a child view in a fragment, and then write a getter method inside the same fragment for this view, which would be called by the Activity to get a reference to the view. Like:
#Override
public View onCreateView(LayoutInflater layoutInflater, ViewGroup container, Bundle savedInstanceState) {
LinearLayout rootLinearLayout = (LinearLayout) layoutInflater.inflate(R.layout.my_fragment, container, false);
javaCameraView = (JavaCameraView) rootLinearLayout.findViewById(R.id.myFragment_javaCameraView);//***********
return rootLinearLayout;
}
JavaCameraView getCameraView() {
return javaCameraView;
}
But the problem is that I need the child view inside the Activity's onCreate(), and I think onCreateView() of fragment is called AFTER the onCreate() of Activity returns. Reference
So is there a way to get a reference to a fragment's view, inside the onCreate() of the Activity?
You are doing it wrong. You should not be exposing a fragment's UI elements to the hosting activity. A fragment should encapsulate it's views and functionality. If you are really only using the fragment as a UI component, create a custom View and use that in the activity.
To answer your question, no, you can't get a reference to the fragment's view in the activity's onCreate(). The reason is that the fragment's view doesn't exist until the fragment has gone through it's lifecycle. There's no guarantee when that's going to happen, which is why it's bad to be coding based on such assumptions.
If the fragment needs to communicate events back to the activity, have your activity implement a listener interface, and have the fragment call that interface when appropriate.
So in your case, you could do something like this in the fragment's onCreateView(),
if (getActivity() instanceof Listener) {
((Listener)getActivity()).onViewCreated(fragmentViews);
}
But again, that's doing it wrong.
To call onCreateView() manually should work, though this is not a good way to do it.
private JavaCameraView javaCameraView;
#Override
public View onCreateView(LayoutInflater layoutInflater, ViewGroup container, Bundle savedInstanceState) {
LinearLayout rootLinearLayout = (LinearLayout) layoutInflater.inflate(R.layout.my_fragment, container, false);
javaCameraView = (JavaCameraView) rootLinearLayout.findViewById(R.id.myFragment_javaCameraView);
return rootLinearLayout;
}
JavaCameraView getCameraView(LayoutInflater layoutInflater) {
if (javaCameraView == null) {
onCreateView(layoutInflater, null, null);
}
return javaCameraView;
}
You can then use yourFragment.getCameraView(getLayoutInflater()) in your onCreate().
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;
}
We all know that when using ViewPager with Fragment and FragmentPagerAdapter we get 3 Fragment loaded: the visible one, and both on each of its sides.
So, if I have 7 Fragments and I'm iterating through them to see which 3 of them are the ones that are loaded, and by that I mean onCreateView() has already been called, how can I determine this?
EDIT: The Fragment doesn't have to be the one that the ViewPager is showing, just that onCreateView() has already been called.
Well logically, this would be a reasonable test if onCreateView has been called:
myFragment.getView() != null;
Assuming you a have a reference to all of the fragments in the pager iterate, them and check if they have a view.
http://developer.android.com/reference/android/app/Fragment.html#getView()
Update
The above answer assumes that your fragments always create a view, and are not viewless fragments. If they are then I suggest sub classing the fragment like so:
public abstract class SubFragment extends Fragment
{
protected boolean onCreateViewCalled = false;
public boolean hasOnCreateViewBeenCalled()
{
return onCreateViewCalled;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup Container, Bundle state){
onCreateViewCalled = true;
return null;
}
}
Just bear in mind that further sub classes will have to call super or set the flag themselves should they override onCreateView as well.
I added an interface to Fragment. Looks like:
protected OnCreateViewCallback createViewCallback = null;
public void setCreateViewCallback(OnCreateViewCallback createViewCallback) {
this.createViewCallback = createViewCallback;
}
public interface OnCreateViewCallback {
void onCreateView();
}
In my onCreateView():
//initialize your view.
if (createViewCallback != null) {
createViewCallback.onCreateView();
createViewCallback = null;
}
return mainView;
From my activity:
if (ocrFragment.getView() == null) {
ocrFragment.setCreateViewCallback(new MainScreenFragment.OnCreateViewCallback() {
#Override
public void onCreateView() {
ocrFragment.ocrImage(picture, false);
}
});
} else {
ocrFragment.ocrImage(picture, false);
}
If you are trying to perform something after onCreateView is called, use onViewCreated:
Called immediately after onCreateView(LayoutInflater, ViewGroup,
Bundle) has returned, but before any saved state has been restored in
to the view. This gives subclasses a chance to initialize themselves
once they know their view hierarchy has been completely created. The
fragment's view hierarchy is not however attached to its parent at
this point.
public void onViewCreated(View view, Bundle savedInstanceState) {
MyActivity myActivity = (MyActivity) getActivity();
MyActivity.newAsyncTask(mPar);
}
You could also check for Fragment.isVisible() because a Fragment is in visible state when it's in the offscreen page limit of a ViewPager.
Edit: But it just really depends on what you really want to achieve with your question. Perhaps some kind of update to all UIs in your Fragments when their UI is ready?
EDIT:
Just another addition, you could listen to onViewCreated() and set a flag. Or notify your Activity and do further work (getActivity() will return your Activity at this point). But really, better state what you want to accomplish with your question.
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().
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?