I'm studying fragments from this link : http://developer.android.com/guide/components/fragments.html
There's a piece of code given as:
public static class ExampleFragment extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.example_fragment, container, false);
}
}
I was confused about the attachToRoot parameter so I looked up on Stack Overflow for some help and found good answers on similar problem. So what I understand is if you set it to true, the fragment gets attached to the activity's root layout, and derives its layoutparams from there. If it's false it would simply return the root of the inflated layout and acts like a standalone view for a fragment (deriving the layout params from the passed in container).
Now I read further down in the documentation regarding attachToRoot for the above example:
A boolean indicating whether the inflated layout should be attached to
the ViewGroup (the second parameter) during inflation. (In this case,
this is false because the system is already inserting the inflated
layout into the container—passing true would create a redundant view
group in the final layout.)
I don't get the last parenthesis statement where it says that it should be false because we're already inserting the layout into the container. What does it mean that we're already inserting into the container without attachToRoot as true? If the parameter is true, how would the final layout have redundant view groups. An example to elaborate this part would be a great help. Thanks.
I don't usually answer my own questions, but after doing a bit more research for this, I thought that maybe this will help someone else. Although Marcin's answer is correct, but I'm just answering a bit more in detail.
As per code :
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.example_fragment, container, false);
}
The second parameter container is a framelayout with id fragment_container that activity uses to add fragment to it's layout.
Now, if we dive deeper into inflate method of LayoutInflater class, this is the code (I'm just highlighting the relevant parts of code rather than the whole part) :
// The view that would be returned from this method.
View result = root;
// Temp is the root view that was found in the xml.
final View temp = createViewFromTag(root, name, attrs, false);
Firstly, it creates a temp view from the supplied root.
In case attachToRoot is true, it does this :
if (root != null && attachToRoot) {
root.addView(temp, params);
}
It adds the temp view created above to the root view (i.e. container).
In case attachToRoot is false, it does this:
if (root == null || !attachToRoot) {
result = temp;
}
As quite evident, in case attachToRoot is true, it simply returns the root (fragment_container i.e the id activity uses to place the fragment inside it.) after adding the temp view to it (Root view in example_fragment in this case)).
In case attachToRoot is false, it simply returns the root of the fragment's xml, i.e. the container parameter is just used to get layoutParams for fragment's root view (since it doesn't have a root, so it needs params from somewhere).
The problem in case of true, arises in above example, because the return value is root (fragment_container with added view temp, and fragment_container by default has a parent already.). Now, if you try to do a fragment transaction, you're trying to add a child view fragment_container(which already has a parent to another xml (framelayout that you defined to add the fragment to).
Due to this, Android throws the following exception:
if (child.getParent() != null) {
throw new IllegalStateException("The specified child already has a parent. " +
"You must call removeView() on the child's parent first.");
}
Problem while setting it to true and returning is that the view returned already has a parent, so can't be used else where. Other way, you could make a separate view group inside onCreateView (maybe a LinearLayout), set the parameter to true, and return the view. Then it'll work fine because the viewgroup won't have an existing parent.
This is my understanding of the above problem, I might be wrong in which case I'd like any Android expert to correct this.
It means that in case on onCreateView() returned View will be attached to container view anyway, so setting it to true in your onCreateView() would cause it to be added twice to container layout, which isn't what you usually want. And setting root view to not null and still have attachToRoot false allows inflated view to derive from root, w/o being added.
check this
View v =getLayoutInflater.inflate(R.layout.example_fragment, viewgroup);
and also this
View v =getLayoutInflater.inflate(R.layout.example_fragment, null);
now if you call this for the first scenario
Log.v("testing",String.valueOf(v.getParent() == null));
you will get false as the output, but when you check that on the second line you will get true.
Simple understanding is if you specify a parent(ViewGroup) it is attached to it, but if you add the last parameter the you are telling the inflater to skip the attachment of the View so your returned View has no parent, but if you specify null, who's going to be attached to? NOBODY so the returned View has no parent, and also like you stated it derives layoutParameters for the View.
If you dont add the last parameter the View will have its parent as the Activity View and hence will be returned in your OncreateView having already a parent, before it will be added, which will cause some Exceptions as a View cannot have different daddies and the Fragment will not be able to manage the View itself.
if add true return null from your oncreateView
The View that you return from onCreateView() method of a fragment, will be attached to the container of the fragment by the system. So the third parameter of inflate() method should be false in this case. If you set it to true then it will be attached to the root twice. So you should set it to false in this case.
Now, the inflated view derives its layout parameters from the second parameter of inflate() method not from the third boolean parameter.
ok from android official documentation Android documentation
What is Fragment?
A Fragment represents a behavior or a portion of user interface in an Activity. You can combine multiple fragments in a single activity to build a multi-pane UI and reuse a fragment in multiple activities. You can think of a fragment as a modular section of an activity, which has its own lifecycle, receives its own input events, and which you can add or remove while the activity is running (sort of like a "sub activity" that you can reuse in different activities).
How to Use Fragment in Activity?
A fragment must always be embedded in an activity and the fragment's lifecycle is directly affected by the host activity's lifecycle. For example, when the activity is paused, so are all fragments in it, and when the activity is destroyed, so are all fragments. However, while an activity is running (it is in the resumed lifecycle state), you can manipulate each fragment independently, such as add or remove them.
What is Fragment Transaction
you can manipulate each fragment independently, such as add or remove them. When you perform such a fragment transaction, you can also add it to a back stack that's managed by the activity—each back stack entry in the activity is a record of the fragment transaction that occurred. The back stack allows the user to reverse a fragment transaction (navigate backwards), by pressing the Back button.
How Fragment and Activity Interact with each other
When you add a fragment as a part of your activity layout, it lives in a ViewGroup inside the activity's view hierarchy and the fragment defines its own view layout. You can insert a fragment into your activity layout by declaring the fragment in the activity's layout file, as a element, or from your application code by adding it to an existing ViewGroup.
Fragment Lifecycle methods onCreateView()
onCreate()
onPause()
And your question is
What does it mean that we're already inserting into the container without attachToRoot as true? If the parameter is true, how would the final layout have redundant view groups
and here is answar
suppose you have added already in your activity layout then you do not' need to make last parameter to true.because each time you perform add or remove of fragment their is a fragment transaction.
However if you have not added already a fragment in your layout then you can set it to true.
read this in doc
You can insert a fragment into your activity layout by declaring the fragment in the activity's layout file, as a element, or from your application code by adding it to an existing ViewGroup. However, a fragment is not required to be a part of the activity layout; you may also use a fragment without its own UI as an invisible worker for the activity.
all fragment transaction are added on activity back stack
Related
I have seen DataBindingUtil used with all three methods, and it is not clear from the documentation (https://developer.android.com/reference/android/databinding/DataBindingUtil) what the difference is between the three.
bind takes an already inflated view hierarchy and returns a ViewDataBinding for it.
inflate takes a layout resource ID, inflates a view hierarchy from it and returns a ViewDataBinding for it. It's essentially equal to
val layoutInflater = LayoutInflater.from(context)
val view = layoutInflater.inflate(R.layout.some_layout, ...)
val binding = DataBindingUtil.bind<SomeLayoutBinding>(view)
setContentView takes a layout resource ID, inflates a view hierarchy from it, sets it as an activity content and returns a ViewDataBinding for the inflated view hierarchy. It's essentially equal to
setContentView(R.layout.some_layout)
val view = findViewById<View>(android.R.id.content)
val binding = DataBindingUtil.bind<SomeLayoutBinding>(view)
Generaly setContentView () will be displayed in the activity.
but fragments have a lifecycle method called onCreateView which returns a view. The most common way to do this is to inflate a view in XML and return it(as may you see in fragment's java code). In this case you need to inflate it yourself. Fragments don't have a setContentView method. so inflate use for fragments.
and binding just bind a view to a layout.
i'm reading this android dev book and im stuck understanding how this line of code is error free (please keep in mind i've gotten rid of some code because for more focus on this part.
public View onCreateView(LayoutInflater layoutToInflate, ViewGroup parent, Bundle saveState)
{
View v = layoutToInflate.inflate(R.layout.activity_main_fragment,parent,false);
return v;
}
from what i believe, i need a method that returns a view because the Class extends from a Fragment class, not an activity so i have to explicitly find the view, the parameters are straight forward what i dont understand is how we create a view and set it equal to layoutToInflate...false;
I think you have a misunderstanding what the concepts of Fragments are. They reside inside an Activity. If a Fragment has a UI, it needs the parent Activity to also have a UI. That also means Fragments have a ViewParentbelonging to an Activity. This parent is given to the Fragment by the ViewGroup parent argument. So when creating a Fragment with a UI, you need to inflate the layout belonging to your Fragment and pass it to the Activity, which adds it to the ViewGroup parent. So that's why you get a LayoutInflater to inflate your Fragment's view:
View v = layoutToInflate.inflate(R.layout.activity_main_fragment,parent,false);
Afterwards you return it to give it to the parent Activity.
layoutToInflate is a variable of LayoutInflater and R.layout.activity_main_fragment is the name of the layout file to be inflated.
I'm building an android app in which I use one Activity from which I can load several fragments. So I start with the FragmentA, and from that I load Fragment B as follows:
FragmentTransaction tx = getSupportFragmentManager().beginTransaction();
tx.replace(R.id.content_frame, detailsFragment).addToBackStack(null).commit();
This works fine. When I press the backbutton, it also goes back to FragmentA perfectly well. The problem is that when I click on the button to go to FragmentB again I get an InflateException (Duplicate id 0x7f08007a, tag null, or parent id 0x0 with another fragment for com.ourcomp.fragment.DetailsFragment) on this line:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_details, null);
}
I think I need to check whether it was inflated already, but I don't really know how. Does anybody know how I can check wether the view was already inflated so that I can prevent this error?
After checking documentation again, I found:
The ViewGroup to be the parent of the inflated layout. Passing the container is important in order for the system to apply layout parameters to the root view of the inflated layout, specified by the parent view in which it's going.
So try to replace
return inflater.inflate(R.layout.fragment_details, null);
with
return inflater.inflate(R.layout.fragment_details, container);
this is a though one for me. I have a MainActiviy which extends FragmentAnctivity. There I have 1 FrameLayout and buttons below to change frame's content. I do so by switching show/hide for created fragments which I added to FrameLayout before in OnCreate.
I'm also nesting more fragments in 1 fragment (As I have 1 fragment for 1 type of content and inside of it there is listFragment which is changed to DetailFragment after OnItemClick... again with show/hide approach).
Problem is that in 2 different contents I have 2 different instances of 1 Fragment class, so those 2 instances use 1 same layout file. And although the first of those fragment is hidden and 2nd is shown, when I change some view through 2nd instance then layout of 1st instance is changed and 2nd remains same as before. (Hope it is understandable)
I guess it's totally a mistake in managing and understanding of fragments' lifecycle, so can please someone help me to solve this?
Thanks very much :)
I suppose you get main point of fragments using practices. Your problem is simple. I almost sure you use getActivity().findViewById(...) calls to access views in your Fragment (or nested Fragment whatever). I this case Activity would return you fist view with defined id from whole your views hierarchy.
Solution is pretty simple - you just must avoid getActivity().findViewById(...) construction and get all links to views in onCreateView() callback and use exact this link with all future operations. Than everything will be ok. Here is simple example:
private TextView mDummyText;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.layout_name, container, false);
initMembersViews(v);
return v;
}
private void initMembersViews(View v) {
mDummyText = (TextView) v.findViewById(R.id.fr_houses_list_text);
}
Hope it would helps you! Good luck!
I am trying to save my View states in my fragment but I am concerned I make be leaking my Activity. Here is what I am doing:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle state){
if(mView != null){
View oldParent = mView.getParent();
if(oldParent != container){
((ViewGroup)oldParent).removeView(mView);
}
return mView;
}
else{
mView = inflater.inflate(R.id.fragview, null)
return mView;
}
}
I am concerned because I know all Views hold onto a context and I don't know if it is the Activity context or Application context if inflated from the inflater. Perhaps it would be a better idea to pragmatically create the view and set its attributes using getActivity().getApplication() rather than use the inflater. I would appreciate any feedback on this.
Thanks!
EDIT: Confirmed Activity leak, although this code works great don't do it :*(
I am trying to save my View states in my fragment but I am concerned I make be leaking my Activity.
Use onSaveInstanceState() (in the fragment) and onRetainConfigurationInstance() (in the activity) for maintaining "View states" across configuration changes. I am not quite certain what other "View states" you might be referring to.
I am concerned because I know all Views hold onto a context and I don't know if it is the Activity context or Application context if inflated from the inflater.
Since using the Application for view inflation does not seem to work well, you should be inflating from the Activity. And, hence, the views will hold a reference to the Activity that inflated them.
#schwiz é have implemented something similar.
I use the setRetainInstance in the fragment. on the fragment layout I have a FrameLayout placeholder where I put my webView inside.
This webView is created programmatically on the Fragment's onCreate using the Application Context, and I do a addView(webView) on the inflated layout in onCreateView().
webView = new WebView(getActivity().getApplicationContext());
And on onDestroyView I simply remove the webView from my framelayout placeholder.
It work really well, unless you try to play a video in fullscreen. That doesn't work because it expects an Activity Context
I am trying to save my View states in my fragment
In order to save and retain view's state you can just use View.onSaveInstanceState () and View.onRestoreInstanceState (Parcelable state)
It will help you to handle saving and restoring view state independantly from neither activity or fragment.
See this answer for more information about it (how to prevent custom views from losing state across screen orientation changes)