Duplicate ID, tag null, or parent id with another fragment for com.google.android.gms.maps.MapFragment

The answer Matt suggests works, but it cause the map to be recreated and redrawn, which isn't always desirable. After lots of trial and error, I found a solution that works for me:

private static View view;

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

For good measure, here's "map.xml" (R.layout.map) with R.id.mapFragment (android:id="@+id/mapFragment"):

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

    <fragment xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/mapFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        class="com.google.android.gms.maps.SupportMapFragment" />
</LinearLayout>

I hope this helps, but I can't guarantee that it doesn't have any adverse effects.

Edit: There were some adverse effects, such as when exiting the application and starting it again. Since the application isn't necessarily completely shut down (but just put to sleep in the background), the previous code i submitted would fail upon restarting the application. I've updated the code to something that works for me, both going in & out of the map and exiting and restarting the application, I'm not too happy with the try-catch bit, but it seem to work well enough. When looking at the stack trace it occurred to me that I could just check if the map fragment is in the FragmentManager, no need for the try-catch block, code updated.

More edits: Turns out you need that try-catch after all. Just checking for the map fragment turned out not to work so well after all. Blergh.


The problem is that what you are trying to do shouldn't be done. You shouldn't be inflating fragments inside other fragments. From Android's documentation:

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.

While you may be able to accomplish the task with the hacks presented here, I highly suggest you don't do it. Its impossible to be sure that these hacks will handle what each new Android OS does when you try to inflate a layout for a fragment containing another fragment.

The only Android-supported way to add a fragment to another fragment is via a transaction from the child fragment manager.

Simply change your XML layout into an empty container (add an ID if needed):

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

Then in the Fragment onViewCreated(View view, @Nullable Bundle savedInstanceState) method:

@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    FragmentManager fm = getChildFragmentManager();
    SupportMapFragment mapFragment = (SupportMapFragment) fm.findFragmentByTag("mapFragment");
    if (mapFragment == null) {
        mapFragment = new SupportMapFragment();
        FragmentTransaction ft = fm.beginTransaction();
        ft.add(R.id.mapFragmentContainer, mapFragment, "mapFragment");
        ft.commit();
        fm.executePendingTransactions();
    }
    mapFragment.getMapAsync(callback);
}

I had the same issue and was able to resolve it by manually removing the MapFragment in the onDestroy() method of the Fragment class. Here is code that works and references the MapFragment by ID in the XML:

@Override
public void onDestroyView() {
    super.onDestroyView();
    MapFragment f = (MapFragment) getFragmentManager()
                                         .findFragmentById(R.id.map);
    if (f != null) 
        getFragmentManager().beginTransaction().remove(f).commit();
}

If you don't remove the MapFragment manually, it will hang around so that it doesn't cost a lot of resources to recreate/show the map view again. It seems that keeping the underlying MapView is great for switching back and forth between tabs, but when used in fragments this behavior causes a duplicate MapView to be created upon each new MapFragment with the same ID. The solution is to manually remove the MapFragment and thus recreate the underlying map each time the fragment is inflated.

I also noted this in another answer [1].