27

I'm trying make an app that I'm building take a search term from the main activity, return results, and then have the results be clickable such that a detail could be viewed from each results. I'm doing this by using a MainActivity, a ResultsActivity, and PlaceActivity and then a ListFragmentClickable (which extends ListFragment). If the handset is oriented in portrait mode, the results list should be viewable, with the details being viewed only if a result is clicked. If the handset is landscape, a detail window should pop up to the right of the list when an item is selected.

At runtime, I get an error that reads "error inflating class fragment". I have no idea what is causing it, and I'd love help in getting rid of it.

My ListFragmentClickables are called by my ResultsActivity, which is here:

public class ResultsActivity extends FragmentActivity implements ListFragmentClickable.OnItemSelectedListener{

private ArrayAdapter<String> mAdapter;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_results_view);

    //Receive searchTerm from MainActivity
    Intent intent = getIntent();
    String searchTerm = intent.getStringExtra(MainActivity.SEARCH_TERM);

    mAdapter = new ArrayAdapter<String>(this, R.layout.item_label_list);

    FragmentManager     fm = getSupportFragmentManager();
    FragmentTransaction ft = fm.beginTransaction();

    FactualResponderFragment responder = (FactualResponderFragment) fm.findFragmentByTag("RESTResponder");
    if (responder == null) {
        responder = new FactualResponderFragment();
        ft.add(responder, "RESTResponder");
    }

    Bundle bundle = new Bundle();
    bundle.putString("search_term", searchTerm);
    responder.setArguments(bundle);

    ft.commit();
}

public ArrayAdapter<String> getArrayAdapter() {
    return mAdapter;
}

@Override //creates a DetailFragment when a list item is selected
public void onItemSelected(String link) {
    DetailFragment fragment = (DetailFragment) getSupportFragmentManager().findFragmentById(R.id.detailFragment);
    if (fragment != null && fragment.isInLayout()) {
          fragment.setText(link);
    } else {
      Intent intent = new Intent(getApplicationContext(), PlaceActivity.class);
          intent.putExtra(PlaceActivity.EXTRA_URL, link);
      startActivity(intent);
    }
}

}

Here's the ListFragmentClickable class:

public class ListFragmentClickable extends ListFragment{

  private OnItemSelectedListener listener;


  public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 
      View view = inflater.inflate(R.layout.activity_results_view, container, false);
      return view;
  }

  @Override
      public void onListItemClick(ListView l, View v, int position, long id) {
          updateDetail(); //see bottom
      }

  public interface OnItemSelectedListener {
      public void onItemSelected(String link);
  }

  @Override
  public void onAttach(Activity activity) {
      super.onAttach(activity);
      if (activity instanceof OnItemSelectedListener) {
          listener = (OnItemSelectedListener) activity;
      } else {
          throw new ClassCastException(activity.toString()
            + " must implemenet ListFragmentClickable.OnItemSelectedListener");
      }
  }


 public void updateDetail() {
        // Create fake data
    String newTime = String.valueOf(System.currentTimeMillis());
    // Send data to Activity
    listener.onItemSelected(newTime); //should direct PlaceActivity!!
  }
}

Here's my layout file for the ResultsActivity (activity_results_activity.xml):

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal" >

<fragment
    android:id="@+id/fragment_content"
    android:layout_width="0dp"
    android:layout_weight="1"
    android:layout_height="match_parent"
    android:layout_marginTop="?android:attr/actionBarSize"
    class="com.example.blobtag2.ListFragmentClickable" ></fragment>


<ListView
    android:id="@android:id/list"
    android:layout_width="fill_parent"
    android:layout_height="50dp"
    android:divider="#b5b5b5"
    android:dividerHeight="1dp"></ListView>

</LinearLayout> 

And finally here's the log:

03-21 22:32:03.297: E/AndroidRuntime(764): FATAL EXCEPTION: main
03-21 22:32:03.297: E/AndroidRuntime(764): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.blobtag2/com.example.blobtag2.ResultsActivity}: android.view.InflateException: Binary XML file line #7: Error inflating class fragment
03-21 22:32:03.297: E/AndroidRuntime(764):  at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2059)
03-21 22:32:03.297: E/AndroidRuntime(764):  at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2084)
03-21 22:32:03.297: E/AndroidRuntime(764):  at android.app.ActivityThread.access$600(ActivityThread.java:130)
03-21 22:32:03.297: E/AndroidRuntime(764):  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1195)
03-21 22:32:03.297: E/AndroidRuntime(764):  at android.os.Handler.dispatchMessage(Handler.java:99)
03-21 22:32:03.297: E/AndroidRuntime(764):  at android.os.Looper.loop(Looper.java:137)
03-21 22:32:03.297: E/AndroidRuntime(764):  at android.app.ActivityThread.main(ActivityThread.java:4745)
03-21 22:32:03.297: E/AndroidRuntime(764):  at java.lang.reflect.Method.invokeNative(Native Method)
03-21 22:32:03.297: E/AndroidRuntime(764):  at java.lang.reflect.Method.invoke(Method.java:511)
03-21 22:32:03.297: E/AndroidRuntime(764):  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:786)
03-21 22:32:03.297: E/AndroidRuntime(764):  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
03-21 22:32:03.297: E/AndroidRuntime(764):  at dalvik.system.NativeStart.main(Native Method)
03-21 22:32:03.297: E/AndroidRuntime(764): Caused by: android.view.InflateException: Binary XML file line #7: Error inflating class fragment
03-21 22:32:03.297: E/AndroidRuntime(764):  at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:704)
03-21 22:32:03.297: E/AndroidRuntime(764):  at android.view.LayoutInflater.rInflate(LayoutInflater.java:746)
03-21 22:32:03.297: E/AndroidRuntime(764):  at android.view.LayoutInflater.inflate(LayoutInflater.java:489)
03-21 22:32:03.297: E/AndroidRuntime(764):  at android.view.LayoutInflater.inflate(LayoutInflater.java:396)
03-21 22:32:03.297: E/AndroidRuntime(764):  at com.example.blobtag2.ListFragmentClickable.onCreateView(ListFragmentClickable.java:32)
03-21 22:32:03.297: E/AndroidRuntime(764):  at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:846)
03-21 22:32:03.297: E/AndroidRuntime(764):  at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1061)
03-21 22:32:03.297: E/AndroidRuntime(764):  at android.support.v4.app.FragmentManagerImpl.addFragment(FragmentManager.java:1160)
03-21 22:32:03.297: E/AndroidRuntime(764):  at android.support.v4.app.FragmentActivity.onCreateView(FragmentActivity.java:272)
03-21 22:32:03.297: E/AndroidRuntime(764):  at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:676)
03-21 22:32:03.297: E/AndroidRuntime(764):  at android.view.LayoutInflater.rInflate(LayoutInflater.java:746)
03-21 22:32:03.297: E/AndroidRuntime(764):  at android.view.LayoutInflater.inflate(LayoutInflater.java:489)
03-21 22:32:03.297: E/AndroidRuntime(764):  at android.view.LayoutInflater.inflate(LayoutInflater.java:396)
03-21 22:32:03.297: E/AndroidRuntime(764):  at android.view.LayoutInflater.inflate(LayoutInflater.java:352)
03-21 22:32:03.297: E/AndroidRuntime(764):  at com.android.internal.policy.impl.PhoneWindow.setContentView(PhoneWindow.java:256)
03-21 22:32:03.297: E/AndroidRuntime(764):  at android.app.Activity.setContentView(Activity.java:1867)
03-21 22:32:03.297: E/AndroidRuntime(764):  at com.example.blobtag2.ResultsActivity.onCreate(ResultsActivity.java:37)
03-21 22:32:03.297: E/AndroidRuntime(764):  at android.app.Activity.performCreate(Activity.java:5008)
03-21 22:32:03.297: E/AndroidRuntime(764):  at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1079)
03-21 22:32:03.297: E/AndroidRuntime(764):  at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2023)
03-21 22:32:03.297: E/AndroidRuntime(764):  ... 11 more
03-21 22:32:03.297: E/AndroidRuntime(764): Caused by: java.lang.IllegalArgumentException: Binary XML file line #7: Duplicate id 0x7f070003, tag null, or parent id 0x0 with another fragment for com.example.blobtag2.ListFragmentClickable
03-21 22:32:03.297: E/AndroidRuntime(764):  at android.support.v4.app.FragmentActivity.onCreateView(FragmentActivity.java:277)
03-21 22:32:03.297: E/AndroidRuntime(764):  at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:676)
03-21 22:32:03.297: E/AndroidRuntime(764):  ... 30 more

Any and all help is greatly, greatly appreciated. Please let me know if there's anything else I can add?

user2163853
  • 889
  • 4
  • 14
  • 24
  • possible duplicate of [Duplicate ID, tag null, or parent id with another fragment for com.google.android.gms.maps.MapFragment](http://stackoverflow.com/questions/14083950/duplicate-id-tag-null-or-parent-id-with-another-fragment-for-com-google-androi) – rds Jun 29 '15 at 16:19

7 Answers7

40

The problem is the fragment on xml is loaded twice, and the second time it is added to the FragmentManager you get a IllegalArgumentException. I got the same problem yesterday.

My solution, I change it to re-create the fragment dynamically instead of define it on the xml file:

Change this xml file part:

<fragment
    android:id="@+id/fragment_content"
    android:layout_width="0dp"
    android:layout_weight="1"
    android:layout_height="match_parent"
    android:layout_marginTop="?android:attr/actionBarSize"
    class="com.example.blobtag2.ListFragmentClickable" ></fragment>

To this instead:

<FrameLayout
    android:id="@+id/fragment_content"
    android:layout_width="0dp"
    android:layout_weight="1"
    android:layout_height="match_parent"
    android:layout_marginTop="?android:attr/actionBarSize" />

An then in the onCreate, yo should replace FrameLayout for a new fragment:

Fragment fragment = new ListFragmentClickable();
FragmentManager     fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.fragment_content, fragment);
ft.commit(); 

And then you have the same but, you won't get a Duplicate ID error.

Zhen
  • 3,823
  • 5
  • 34
  • 53
  • I thought that defining XML statically for fragments was an acceptable design practice? I am so confused. – theblang Jan 24 '14 at 15:55
  • 1
    @mattblang, i think you are right, but his problem is using the same XML with unique id twice, so he need to load dinamically. – Zhen Jan 25 '14 at 12:51
  • The problem is not the nested fragment, its the fragment id (as Zhen said before). – LuckyMalaka Apr 23 '14 at 17:47
  • This method worked for me. A lot of docs ive read say to just handle fragment initialization programatically, especially if you're switching between 2 or more at a time. It seems that so long as the fragments are static, the XML method will work. – Nlinscott Oct 18 '14 at 01:37
38

It occurs when fragments are defined in XML (statically): FragmentManager doesn't manage child fragments if parent fragment is destroyed. Then, it breaks ("duplicate id" error) when the XML is inflated for the second time.

I bypass this problem removing the XML fragment manually when parent is destroyed, with this code in the parent fragment:

@Override
public void onDestroyView() {

    FragmentManager fm = getFragmentManager();

    Fragment xmlFragment = fm.findFragmentById(R.id.XML_FRAGMENT_ID);
    if (xmlFragment != null) {
        fm.beginTransaction().remove(xmlFragment).commit();
    }

    super.onDestroyView();
}

Note for copypasters: XML_FRAGMENT_ID is the id of the fragment in the XML ;)


Furthermore, I prefer a new class that wraps XML fragments. It simplyfies the code since you just need to extend your fragment class from it. Add this class to your project:

package net.treboada.mytests.fragments;

import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.util.AttributeSet;

public class XmlFragment extends Fragment {

    @Override
    public void onInflate(Activity activity, AttributeSet attrs,
            Bundle savedInstanceState) {

        FragmentManager fm = getFragmentManager();
        if (fm != null) {
            fm.beginTransaction().remove(this).commit();
        }

        super.onInflate(activity, attrs, savedInstanceState);
    }
}

Then, extend your XML fragment classes:

package net.treboada.mytests.fragments;

public class TestXmlFragment01 extends XmlFragment {

    // ...

}

and voilà! :)

caligari
  • 2,030
  • 20
  • 23
  • tl;dr: Don't use fragments in XML. – Zenexer Jan 15 '14 at 09:25
  • @Zenexer Why not? IMHO, fragments in XML are useful in order to work with designers (collaborating with coders). – caligari Jan 20 '14 at 08:49
  • Wouldn't it just be getFragmentManager().beginTransaction().remove(this).commit(); ? –  Feb 06 '14 at 16:14
  • @dpk The FragmentManager couldn't be available at early stages. Testing null is just a precaution to avoid null-pointer exceptions. – caligari Feb 07 '14 at 09:44
  • @caligari Oh, sorry, I meant as opposed to having to findFragmentById first. –  Feb 07 '14 at 15:21
  • 1
    Neverthelesss after using this class, if you have a click listener on a button in your fragment, and try to call YourFragment.this.getActivity().finish(), it crashes because it seems it can't get the activity. Not sure why yet. – Benjamin Piette Oct 07 '14 at 13:05
  • Doesn't recommend the second solution. It could make `getActivity` throw an exception as soon as the fragment is removed, which is shortly after when the fragment is created. The first solution works fine though. :/ – Mygod Apr 25 '15 at 09:52
  • @Mygod It couldn't: the method overrides `onInflate()` and it's always called before parent method (`super.onInflate()` is the last line). – caligari May 05 '15 at 10:23
  • Yes It could because the removal was committed which means it is not executed immediately. And according to my experience, the removal happens after `onCreateView`. :/ – Mygod May 05 '15 at 23:38
  • It is supposed that _FragmentTransaction_ is able to manage those situations on _Fragment/Activity lifecycles_. What type of exception and message had you experienced? Is this the case where [`commitAllowingStateLoss()`](http://developer.android.com/reference/android/app/FragmentTransaction.html#commitAllowingStateLoss()) is needed? – caligari May 07 '15 at 07:37
11

Let's try this out. I got the same problem and i fixed my problem using this code.

 View root;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    if (root!= null) {
        ViewGroup parent = (ViewGroup) root.getParent();
        if (parent != null)
            parent.removeView(root);
    }
    try {
        root= inflater.inflate(R.layout.map_layout, container, false);
    } catch (InflateException e) {
        /* map is already there, just return view as it is */
    }
    return root;
}
RayChongJH
  • 361
  • 3
  • 5
2

If you were reading the other answers here feeling that they seemed correct, look here https://stackoverflow.com/a/19815266/1139784 because it references the documentation explaining how this is not supported (at least in my case, it is hard to tell from this question whether the layout fragment nesting is happening)

Note: You cannot inflate a layout into a fragment when that layout includes a <fragment>.
Nested fragments are only supported when added to a fragment dynamically.

More data here Best practice for nested fragments in Android 4.0, 4.1 (<4.2) without using the support library

Edit: Also, if you're considering using nested fragments and don't need the lifecycle management, you can create a custom view group (like extending LinearLayout) instead. http://www.vogella.com/tutorials/AndroidCustomViews/article.html

Community
  • 1
  • 1
Eric Woodruff
  • 5,907
  • 3
  • 31
  • 32
  • Thank you! This limitation is not well documented, in the official Fragment documentation. It get me crazy for a while... – ARLabs Feb 12 '16 at 09:52
2

You can extend you fragment (which you load in xml) from XmlFragment. It handles parent FragmentManager and removes itself.

public class XmlFragment extends BaseFragment {

    @Override
    public void onDestroyView() {
        Fragment parentFragment = getParentFragment();
        FragmentManager manager;
        if (parentFragment != null) {
            // If parent is another fragment, then this fragment is nested
            manager = parentFragment.getChildFragmentManager();
        } else {
            // This fragment is placed into activity
            manager = getActivity().getSupportFragmentManager();
        }
        manager.beginTransaction().remove(this).commitAllowingStateLoss();
        super.onDestroyView();
    }
}
Oleksandr
  • 5,446
  • 42
  • 52
1

I had the same problem with fragment called twice and on the second time it crashed. Solution is to implement the remove statement in onDetach method:

    @override
public void onDetach(){
super.onDetach();
FragmentManager fm = getFragmentManager()

Fragment xmlFragment = fm.findFragmentById(R.id.yourfragmentid);
if(xmlFragment != null){
    fm.beginTransaction().remove(xmlFragment).commit();
    }
}
0

This line

03-21 22:32:03.297: E/AndroidRuntime(764): Caused by: java.lang.IllegalArgumentException: Binary XML file line #7: Duplicate id 0x7f070003, tag null, or parent id 0x0 with another fragment for com.example.blobtag2.ListFragmentClickable

specifically the part at the end

Duplicate id 0x7f070003, tag null, or parent id 0x0 with another fragment for com.example.blobtag2.ListFragmentClickable

is telling you that at some point in the lifetime of your activity you're trying to create a view for ListFragmentClickable when one already exists. You need to figure out how this is occurring.

j__m
  • 8,784
  • 1
  • 26
  • 49