6

Originally I got this error:

The specified child already has a parent. You must call removeView() on the child's parent first

at

customSection.addView(customLayout);

So I added

((LinearLayout)customLayout.getParent()).removeView(customLayout);

and now get

java.lang.NullPointerException

So if the child has a parent, and I must first remove the child from the parent, why does getParent() return null?

I have an abstract fragment that allows derived classes to supply a custom layout for the list adapter. Relevant code:

Binding:

public void bind(DataObject row) {
    View customLayout = getChildItemView(row);
    if (customLayout != null) {
        ((LinearLayout) customLayout.getParent()).removeView(customLayout);
        customSection.removeAllViews();
        customSection.addView(customLayout);
        customSection.setVisibility(View.VISIBLE);
    } else {
        customLayout.setVisibility(View.INVISIBLE);
    }

}

protected View getChildItemView(CommonRow row) {
    if (parentView == null) {
        parentView = (LinearLayout) LayoutInflater.from(getActivity())
                .inflate(R.layout.list_item_custom_section,
                        new LinearLayout(getActivity()), true);
        label = (TextView) parentView.findViewById(R.id.txtData1Label);
        value = (TextView) parentView.findViewById(R.id.txtData1Value);
    }
    label.setText("Minimum");
    value.setText(manager.formatMoney(((SpecificDataRow) row).minimum));
    return parentView;
}

I've also tried inflater.inflate(R.layout.list_item_custom_section, null) ... false, null / false, what gives?

EDIT:

@allprog, I knew some cleanup was needed. I wrote this at the end of the day somewhat in a hurry. I have since cleaned up the code, and separated out the binding and inflating of the view. Cleaned up code:

private class ViewHolder {
....

       public ViewHolder(View v) {
            Butterknife.inject(this, v);
            View custom = createCustomView(customSection);
            if (custom != null) {
                customSection.setVisibility(View.VISIBLE);
                customSection.addView(custom);
            }
        }

        public void bind(CommonRow row) {
            ......

            bindCustomView(row, customSection);
        }

}

Child class:

    @Override
    protected View createCustomView(ViewGroup parent) {
        return LayoutInflater.from(getActivity()).inflate(R.layout.list_item_custom_section, parent, false);
    }


    @Override
    protected void bindCustomView(CommonRow row, ViewGroup section) {
        TextView label = Views.findById(section, R.id.txtData1Label);
        TextView value = Views.findById(section, R.id.txtData1Value);

        label.setText("Minimum");
        value.setText(manager.formatMoney(((SpecificRow) row).minimum));
    }

suitianshi got it first, with my original [unkempt] code that was the solution.

Jack
  • 9,156
  • 4
  • 50
  • 75
  • 1
    What is `customSection`? Can you show the code where it is first initialized? – J Steven Perry Feb 10 '14 at 22:03
  • It is in the getChildItemView(CommonRow row) method. parentView = (LinearLayout)LayoutInflater.from(getActivity()).inflate(R.layout.list_item_custom_section, new LinearLayout(getActivity()), true); – Jack Feb 10 '14 at 22:04
  • Can you post the XML for list_item_custom_section? I suspect that you are trying to add the view to itself. Plus the variable `customSection` would still need to be declared (and set) somewhere. Can you post that code? – J Steven Perry Feb 10 '14 at 22:14
  • There's something missing - as @JStevenPerry asked, the `customSection` initialization is nowhere. What you've answered is the `customLayout` inflater. We'd need to see how's `customSection` defined and what it even is. – nKn Feb 10 '14 at 23:10
  • Try to cleanup your code. As @Dmide points it out, you use a field for parentView. This is a bad idea. Also, `getChildItemView` despite its name actually has side effects. I guess, if you clean up this chaos, everything will start working as it should. (PS: the debugger is your friend!) – allprog Feb 11 '14 at 11:42

3 Answers3

12

try this:

public void bind(DataObject row) {
    View customLayout = getChildItemView(row);
    if (customLayout != null) {
         if(customLayout.getParent() != null) {
             ((LinearLayout)customLayout.getParent()).removeView(customLayout);
         }

         customSection.removeAllViews();
         customSection.addView(customLayout);
         customSection.setVisibility(View.VISIBLE);
    } else {
         customLayout.setVisibility(View.INVISIBLE);
    }

}

I have read related source code, getParent should return non-null value when view has a parent. You should make sure it actually has a parent before casting and calling removeView

Wish this helps.

source code :

in View :

public final ViewParent getParent() {
        return mParent;
    }

in ViewGroup.addViewInner

if (child.getParent() != null) {
     throw new IllegalStateException("The specified child already has a parent. " +
         "You must call removeView() on the child's parent first.");
}
suitianshi
  • 3,300
  • 1
  • 17
  • 34
  • I understand that part, the problem is that customSection.addView(customLayout) fails with the error in OP title. So I can check for null all day but it still does not help me remove the customLayout from its original, [apparantly null] parent. Does not make sense to me. – Jack Feb 08 '14 at 05:04
  • added source code to my answer. You mean you get that exception but `getParent` reuturns null? that's really strange! – suitianshi Feb 08 '14 at 05:07
  • and did you try casting `ViewParent` to `ViewGroup` instead of `LinearLayout` ? – suitianshi Feb 08 '14 at 05:10
  • All of the addView methods in ViewGroup eventually go through `addViewInner()`. This is where the IllegalStateException comes from. If `getParent()` returns null, this never gets thrown in the first place. Are you absolutely sure you're not accidentally adding the same view to the same parent more than once? https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/view/ViewGroup.java – Krylez Feb 10 '14 at 22:18
2

As I can see your parentView is a field variable, this is the key. So what I suspect is really going on:

First call of bind(): you creating parentView and it is not have a parent yet, customSection.addView(customLayout); works fine, but after you added a check for parent it fails here.

Second call of bind(): parentView is now have a parent and your added check should work now, but you failed at the previous step. Without a check you are failing here with exception in title.

Solution: check for the presence of parent and remove it only if necessery.

Dmide
  • 6,422
  • 3
  • 24
  • 31
0

First of all LayoutInflater inflate method always returns view without parent.

if attachToRoot == true the parentView will be that new LinearLayout(getActivity())

if attachToRoot == false the parentView will be inflated R.layout.list_item_custom_section whatever it is.

in both cases the ((LinearLayout) customLayout.getParent()) will be null. Thats why you are getting NullPointerException. You can see it in return statement in LayoutInflater documentation.

As is written above declaring parentView as field is bad aproach, it should be method parameter that you will inflate if == null (approach from AdapterView).

BTW: line 9 in your code if would be called it would throw NullPointerException because it is called only in case that customLayout == null!!!

user3098756
  • 340
  • 4
  • 10