Why do we need to Inflate a layout and attachToRoot in Android? - java
I was given this code:
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater!!.inflate(R.layout.fragment_wednesday, container, false)
}
and I don't understand why we need to Inflate a layout and write attachToRoot value.
btw, why do we need a viewGroup?
A layout definition is just some XML data but to really show a layout it must be converted to a tree of objects. The inflater does that.
A container (ViewGroup) is necessary to control where (in a larger tree of view objects) the inflated subtree should be placed.
consider this code
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup fragment_container, Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.example_fragment, fragment_container, false);
}
The second parameter fragment_container is a container(framelayout) with id fragment_container that activity uses to add view of fragment in its layout.
No if we read source code of inflate method of LayoutInflater class, we get this( i removed unnecessary shit here to make you understand code better)
// 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 false, it does this :
// Decide whether to return the root that was passed in or the
// top view found in xml.
if (root == null || !attachToRoot) {
result = temp;
}
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).
In case attachToRoot is true, it does this :
// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
if (root != null && attachToRoot) {
root.addView(temp, params);
}
It adds the temp view created above to the root view (i.e. container).
The main difference between the "third" parameter attachToRoot being true or false is this.
true : add the child view to parent RIGHT NOW
false: add the child view to parent NOT NOW. Add it later. `
When is that later?
That later is when you use for eg parent.addView(childView)
A common misconception is, if attachToRoot parameter is false then the child view will not be added to parent. WRONG
In both cases child view will be added to parentView. It is just the matter of time.
inflater.inflate(child,parent,false);
parent.addView(child);
is equivalent to
inflater.inflate(child,parent,true);
NOTE !! NOTE !! NOTE !!
You should never pass attachToRoot as true when you are not responsible for adding the child view to parent.
Eg When adding Fragment
public View onCreateView(LayoutInflater inflater,ViewGroup parent,Bundle bundle)
{
super.onCreateView(inflater,parent,bundle);
View v = inflater.inflate(R.layout.image_fragment,parent,false);
return v;
}
now , if you pass third parameter as true you will get IllegalStateException because of the following piece of code.
getSupportFragmentManager()
.beginTransaction()
.add(parent, childFragment)
.commit();
Since you have already added the child fragment in onCreateView() by mistake. Calling add will tell you that child view is already added to parent , hence IllegalStateException. This Exception comes from the following piece of code which can be found while inspecting inflate method in LayoutInflater class
if (child.getParent() != null) {
throw new IllegalStateException("The specified child already has a parent. " +
"You must call removeView() on the child's parent first.");
}
Here you are not responsible for adding childView, FragmentManager is responsible. So always pass false in this case
Related
Fragment View's state is broken by FragmentManager
I wanted to retain views through fragment replace operation and noticed a strange bug: After fragment's View is destroyed through replace operation, it still has a parent, however, the parent doesn't have the view among its children. private View view; #Override public View onCreateView(...) { if (view == null) { view = // ... inflate view } else { // at this point, view has mParent, but can't be removed // because the parent does not have it among its children } return view; } #Override public void onDestroyView() { // I could remove the view from its parent here manually, // however, it will cause fragment's content to disappear before // the replace animation ends } Notice how the view has a reference to its parent through mParent, but the parent has zero mChildrenCount. This causes fragment transaction to crash with an exception when trying to reuse the view: java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first. I find this behavior to be exceptionally wrong since it leaves the fragment's view in an unusable state. If someone has faced this issue, please share your workarounds. I have come up with a temporary dirty reflective solution: #Override public View onCreateView(...) { if (view == null) { view = // ... inflate view } else { // clear parent reference (because SDK fails to do it) Field f = View.class.getDeclaredField("mParent"); f.setAccessible(true); f.set(view, null); } return view; }
The specified child already has a parent. You must call removeView() on the child's parent first (Android)
I have to switch between two layouts frequently. The error is happening in the layout posted below. When my layout is called the first time, there doesn't occur any error and everything's fine. When I then call a different layout (a blank one) and afterwards call my layout a second time, it throws the following error: > FATAL EXCEPTION: main > java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first. My layout-code looks like this: tv = new TextView(getApplicationContext()); // are initialized somewhere else et = new EditText(getApplicationContext()); // in the code private void ConsoleWindow(){ runOnUiThread(new Runnable(){ #Override public void run(){ // MY LAYOUT: setContentView(R.layout.activity_console); // LINEAR LAYOUT LinearLayout layout=new LinearLayout(getApplicationContext()); layout.setOrientation(LinearLayout.VERTICAL); setContentView(layout); // TEXTVIEW layout.addView(tv); // <========== ERROR IN THIS LINE DURING 2ND RUN // EDITTEXT et.setHint("Enter Command"); layout.addView(et); } } } I know this question has been asked before, but it didn't help in my case.
The error message says what You should do. // TEXTVIEW if(tv.getParent() != null) { ((ViewGroup)tv.getParent()).removeView(tv); // <- fix } layout.addView(tv); // <========== ERROR IN THIS LINE DURING 2ND RUN // EDITTEXT
simply pass the argument attachtoroot = false View view = inflater.inflate(R.layout.child_layout_to_merge, parent_layout, false);
I came here on searching the error with my recyclerview but the solution didn't work (obviously). I have written the cause and the solution for it in case of recyclerview. Hope it helps someone. The error is caused if in the onCreateViewHolder() the following method is followed: layoutInflater = LayoutInflater.from(context); return new VH(layoutInflater.inflate(R.layout.single_row, parent)); Instead it should be return new VH(layoutInflater.inflate(R.layout.single_row, null));
I got this message while trying to commit a fragment using attach to root to true instead of false, like so: return inflater.inflate(R.layout.fragment_profile, container, true) After doing: return inflater.inflate(R.layout.fragment_profile, container, false) It worked.
You must first remove the child view from its parent. If your project is in Kotlin, your solution will look slightly different than Java. Kotlin simplifies casting with as?, returning null if left side is null or cast fails. (childView.parent as? ViewGroup)?.removeView(childView) newParent.addView(childView) Kotlin Extension Solution If you need to do this more than once, add this extension to make your code more readable. childView.removeSelf() fun View?.removeSelf() { this ?: return val parentView = parent as? ViewGroup ?: return parentView.removeView(this) } It will safely do nothing if this View is null, parent view is null, or parent view is not a ViewGroup
frameLayout.addView(bannerAdView); <----- if you get error on this line the do like below.. if (bannerAdView.getParent() != null) ((ViewGroup) bannerAdView.getParent()).removeView(bannerAdView); frameLayout.addView(bannerAdView); <------ now added view
If other solution is not working like: View view = inflater.inflate(R.layout.child_layout_to_merge, parent_layout, false); check for what are you returning from onCreateView of fragment is it single view or viewgroup? in my case I had viewpager on root of xml of fragment and I was returning viewpager, when i added viewgroup in layout i didnt updated that i have to return viewgroup now, not viewpager(view).
My error was define the view like this: view = inflater.inflate(R.layout.qr_fragment, container); It was missing: view = inflater.inflate(R.layout.qr_fragment, container, false);
In my case it happens when i want add view by parent to other view View root = inflater.inflate(R.layout.single, null); LinearLayout lyt = root.findViewById(R.id.lytRoot); lytAll.addView(lyt); // -> crash you must add parent view like this View root = inflater.inflate(R.layout.single, null); LinearLayout lyt = root.findViewById(R.id.lytRoot); lytAll.addView(root);
Simplified in KOTLIN viewToRemove?.apply { if (parent != null) { (parent as ViewGroup).removeView(this) } }
In my case, I had id named as "root" for constraint layout, which was conflicting the existing parent root id. Try to change the id. <androidx.constraintlayout.widget.ConstraintLayout android:id="#+id/root" //<--It should not named as root. android:layout_width="match_parent" android:layout_height="match_parent" </androidx.constraintlayout.widget.ConstraintLayout>
In my case the problem was caused by the fact that I was inflating parent View with <merge> layout. In this case, addView() caused the crash. View to_add = inflater.inflate(R.layout.child_layout_to_merge, parent_layout, true); // parent_layout.addView(to_add); // THIS CAUSED THE CRASH Removing addView() helped to solve the problem.
The code below solved it for me: #Override public void onDestroyView() { if (getView() != null) { ViewGroup parent = (ViewGroup) getView().getParent(); parent.removeAllViews(); } super.onDestroyView(); } Note: The error was from my fragment class and by overriding the onDestroy method like this, I could solve it.
My problem is related to many of the other answers, but a little bit different reason for needing to make the change... I was trying to convert an Activity to a Fragment. So I moved the inflate code from onCreate to onCreateView, but I forgot to convert from setContentView to the inflate method, and the same IllegalStateException brought me to this page. I changed this: binding = DataBindingUtil.setContentView(requireActivity(), R.layout.my_fragment) to this: binding = DataBindingUtil.inflate(inflater, R.layout.my_fragment, container, false) That solved the problem.
You just need to pass attachToRoot parameter false. mBinding = FragmentCategoryBinding.inflate(inflater, container, false)
If you're using ViewBinding, make sure you're referring to the right binding! I had this issue when I was trying to inflate a custom dialog from within an activity: AlertDialog.Builder builder = new AlertDialog.Builder(this); final AlertBinding alertBinding = AlertBinding.inflate(LayoutInflater.from(this), null, false); builder.setView(binding.getRoot()); // <--- I was using binding (which is my Activity's binding), instead of alertBinding.
This is how I do my custom dialog AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this); android.view.View views = getLayoutInflater().inflate(layout_file, null, false); builder.setView(views); dialog = builder.create(); dialog.show(); I changed it into this and its works for me, I hope this helps Dialog dialog = new Dialog(MainActivity.this); dialog.setContentView(layout_file); dialog.show();
check if you already added the view if (textView.getParent() == null) layout.addView(textView);
if(tv!= null){ ((ViewGroup)tv.getParent()).removeView(tv); // <- fix }
I was facing the same error, and look what I was doing. My bad, I was trying to add the same view NativeAdView to the multiple FrameLayouts, resolved by creating a separate view NativeAdView for each FrameLayout, Thanks
In my case I was accidentally returning a child view from within Layout.onCreateView() as shown below: #Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View v= inflater.inflate(R.layout.activity_deliveries, container, false); RecyclerView rv = (RecyclerView) v.findViewById(R.id.deliver_list); return rv; // <- here's the issue } The solution was to return the parent view (v) instead of the child view (rv).
I found another fix: if (mView.getParent() == null) { myDialog = new Dialog(MainActivity.this); myDialog.setContentView(mView); createAlgorithmDialog(); } else { createAlgorithmDialog(); } Here i just have an if statement check if the view had a parent and if it didn't Create the new dialog, set the contentView and show the dialog in my "createAlgorithmDialog()" method. This also sets the positive and negative buttons (ok and cancel buttons) up with onClickListeners.
In my case, I had an adapter which worked with a recyclerView, the items that were being passed to the adapter were items with their own views. What was required was just a LinearLayout to act as a container for every item passed, so what I was doing was to grab the item in the specified position inside onBindViewHolder then add it to the LinearLayout, which was then displayed. Checking the basics in docs, When an item scrolls off the screen, RecyclerView doesn't destroy its view Therefore, with my items, when I scroll towards a direction, then change towards the opposite direction - fast, the racently displayed items have not been destroyed, meaning, the items are still associated with the LinearLayout container, then on my end, I'm trying to attach to another container, which ends up with a child having a parent already. My solution was to check if the specified item has a parent, if it has, I assign it to a variable, then call parentVar.removeView(item), then assign the new parent. Here's the sample code(Problematic Adapter): override fun onBindViewHolder(holder: QuestionWidgetViewHolder, position: Int) { holder.linearLayoutContainer.removeAllViewsInLayout() val questionWidget: QuestionWidget = dataSource[position] questionWidget.setValueChangedListener(this) holder.linearLayoutContainer.addView(questionWidget)/*addView throws error once in a while*/ } inner class QuestionWidgetViewHolder(mView: View) : RecyclerView.ViewHolder(mView) { val linearLayoutContainer: LinearLayout = mView.findViewById(R.id.custom_question_widget_container) } Content of the R.id.custom_question_widget_container: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="#+id/custom_question_widget_container" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="10dp" android:orientation="vertical" android:padding="10dp" /> So, the questionWidget seems to have been retaining the parent for almost 4 steps outside visibility, and when I scroll to the opposite direction fast, I would encounter it still with its parent, then I'm attempting to add it to another container. Here's the fix - option 1: override fun onBindViewHolder(holder: QuestionWidgetViewHolder, position: Int) { holder.linearLayoutContainer.removeAllViewsInLayout() val questionWidget: QuestionWidget = dataSource[position] questionWidget.setValueChangedListener(this) val initialWidgetParent : ViewParent? = questionWidget.parent //attempt to detach from previous parent if it actually has one (initialWidgetParent as? ViewGroup)?.removeView(questionWidget) holder.linearLayoutContainer.addView(questionWidget) } Another better solution - option 2: override fun onBindViewHolder(holder: QuestionWidgetViewHolder, position: Int) { holder.linearLayoutContainer.removeAllViewsInLayout() val questionWidget: QuestionWidget = dataSource[position] questionWidget.setValueChangedListener(this) val initialWidgetParent : ViewParent? = questionWidget.parent //if it's in a parent container already, just ignore adding it to a view, it's already visible if(initialWidgetParent == null) { holder.linearLayoutContainer.addView(questionWidget) } } Actually, it's much of asking the child if it has a parent before adding it to a parent.
I tried all the things that you guys suggested, with no luck. But, I managed to fix it by moving all my binding initializations from onCreate to onCreateView. onCreate(){ binding = ScreenTicketsBinding.inflate(layoutInflater) } MOVE TO onCreateView(...){ binding = ScreenTicketsBinding.inflate(layoutInflater) }
You can use this methode to check if a view has children or not . public static boolean hasChildren(ViewGroup viewGroup) { return viewGroup.getChildCount() > 0; }
My case was different the child view already had a parent view i am adding the child view inside parent view to different parent. example code below <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="#dimen/lineGap" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#color/black1" android:layout_gravity="center" android:gravity="center" /> </LinearLayout> And i was inflating this view and adding to another LinearLayout, then i removed the LinaarLayout from the above layout and its started working below code fixed the issue: <?xml version="1.0" encoding="utf-8"?> <TextView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" android:textColor="#color/black1" />
It happened with me when I was using Databinding for Activity and Fragments. For fragment - in onCreateView we can inflate the layout in traditional way using inflater. and in onViewCreated method, binding object can be updated as binding = DataBindingUtil.getBinding<FragmentReceiverBinding>(view) as FragmentReceiverBinding It solved my issue
In my case, I was doing this (wrong): ... TextView content = new TextView(context); for (Quote quote : favQuotes) { content.setText(quote.content); ... instead of (good): ... for (Quote quote : favQuotes) { TextView content = new TextView(context); content.setText(quote.content); ...
If you are working with MaterialAlertDialog, this worked for me: (yourChildView.parent as? ViewGroup)?.removeView(yourChildView)
If in your XML you have layout with id "root" It`s problem, just change id name
How did I stop IllegalStateExceptions from being thrown with LayoutInflater().inflate(int resource, ViewGroup root, boolean attachToRoot)?
I recently recently ran into an issue while setting up a viewpager with 3 fragments. When the application ran it crashed with a java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first Here is the code for creating list list of fragments to pass to the pageAdapter. private Vector<Fragment> getFragments() { Vector<Fragment> list = new Vector<Fragment>(); list.add(new Fragment1()); list.add(new Fragment2()); list.add(new Fragment3()); return list; Each of the fragments were essentially the same except for a being created with different layouts. Here was the original code I had for one of the fragments. public class Fragment1 extends Fragment { #Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View v = getActivity().getLayoutInflater().inflate(R.layout.fragment1, container); return v; } } But when I ran it like this it kept crashing with the IllegalStateException. I found that the issue was coming from the fragments being created. After some Googling I tried changing the code for the fragment to this. public class Fragment1 extends Fragment { #Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View v = getActivity().getLayoutInflater().inflate(R.layout.fragment1, container, false); return v; } } This resolved the issue and I no longer got the IllegalStateException except I have no idea how this worked. What exactly does this boolean do? And what does this exception mean? I had tried adding the method call like it suggusted but that did not resolve it. Also I tried changing this boolean to true and I got the same error again. The android docs say its attachToRoot but isn't that what I want to do? Attach my 3 fragments to the rootview, which is the viewpager? If anyone could explain this it would be greatly appreciated.
The boolean parameter of the 3-arg version of LayoutInflater.inflate() determines whether the LayoutInflater will add the inflated view to the specified container. For fragments, you should specify false because the Fragment itself will add the returned View to the container. If you pass true or use the 2-arg method, the LayoutInflater will add the view to the container, then the Fragment will try to do so again later, which results in the IllegalStateException.
Set id and get id of inflated view?
I have to inflate the group of items in a listview and i need to find inflated view anywhere in the program . May I know how to find the view. How to set id and get id of inflated listview. Thanks in Advance
In the Adapter of your list view public View getView(int position, View convertView, ViewGroup parent) { View vi = convertView; if (convertView == null) vi = inflater.inflate(R.layout.newlistrow, null); //Access your inflated layout using vi TextView sometextview= (TextView) vi.findViewById(R.id.textview); // sometextview
Typically you don't find views in a list view. The adapter behind the list view will pass you the view plus the underlying position of your data that you need to render via the getView method. To answer your question of how to get or set id of inflated view you should call getId or setId on the inflated view. http://developer.android.com/reference/android/view/View.html#setId(int) http://developer.android.com/reference/android/view/View.html#getId() View myView = LayoutInflater.from(activity).inflate(R.layout.list_item_punch_log, null); myView.setId(android.R.id.button1); int id = myView.getId(); However doing this and finding views in a list view is a very dangerous act as it will cause some really funny behaviors on the listview. Maybe you can tell us what you are trying to do and we can guide you from there.
What is "convertView" parameter in ArrayAdapter getView() method
Can someone tell me what the convertView parameter is used for in the getView() method of the Adapter class? Here is a sample code take from here: #Override public View getView(int position, View convertView, ViewGroup parent) { View v = convertView; if (v == null) { LayoutInflater vi = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE); v = vi.inflate(R.layout.row, null); } Order o = items.get(position); if (o != null) { TextView tt = (TextView) v.findViewById(R.id.toptext); TextView bt = (TextView) v.findViewById(R.id.bottomtext); if (tt != null) { tt.setText("Name: "+o.getOrderName()); } if(bt != null){ bt.setText("Status: "+ o.getOrderStatus()); } } return v; } What should we pass via convertView? What I've found, take from here: Get a View that displays the data at the specified position in the data set. You can either create a View manually or inflate it from an XML layout file. When the View is inflated, the parent View (GridView, ListView...) will apply default layout parameters unless you use inflate(int, android.view.ViewGroup, boolean) to specify a root view and to prevent attachment to the root. Parameters position -- The position of the item within the adapter's data set of the item whose view we want. convertView -- The old view to reuse, if possible. Note: You should check that this view is non-null and of an appropriate type before using. If it is not possible to convert this view to display the correct data, this method can create a new view. parent -- The parent that this view will eventually be attached to Returns returns -- A View corresponding to the data at the specified position.
You shouldn't be calling that method by yourself. Android's ListView uses an Adapter to fill itself with Views. When the ListView is shown, it starts calling getView() to populate itself. When the user scrolls a new view should be created, so for performance the ListView sends the Adapter an old view that it's not used any more in the convertView param.