10

I'm trying to show a MapView inside a fragment (using the hacked compatibility library). The following has worked just fine in the past:

  • fragment's onCreateView() simply returns a new FrameLayout
  • fragment's onActivityCreated() gets the MapView from the Acitivity and adds it to its view hierarchy
  • onDestroyView() removes the MapView from its view hierarchy

Now I would like the fragment to use a layout defined in xml so that I can have some other UI stuff. Putting the MapView element in the layout file always crashes, so I'm doing it this way:

map_screen_fragment.xml

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


    <FrameLayout
        android:id="@+id/map_container"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" >
    </FrameLayout>

</LinearLayout>

My MapScreenActivity holds the actual MapView, and the fragment calls getMapView(), so I don't run into the "can't have more than one MapView" issue:

MapScreenActivity.java

public class MapScreenActivity extends FragmentActivity {
    protected Fragment fragment;
    protected MapView mapView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.single_pane_empty);

        if (savedInstanceState == null) {
            fragment = new MapScreenFragment();

            getSupportFragmentManager().beginTransaction().add(R.id.root_container, fragment)
                    .commit();
        }
    }

    public MapView getMapView() {
        if (mapView == null) {
            mapView = new MapView(this, getResources().getString(R.string.maps_api_key));
        }

        return mapView;
    }
}

MapScreenFragment.java

public class MapScreenFragment extends Fragment {
    protected ViewGroup mapContainer;
    protected MapView mapView;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle args) {
        View root = inflater.inflate(R.layout.map_screen_fragment, container);
        mapContainer = (ViewGroup) root.findViewById(R.id.map_container);
        return root;
    }

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        mapView = ((MapScreenActivity) getActivity()).getMapView();
        mapView.setClickable(true);
        mapView.setBuiltInZoomControls(true);

        mapContainer.addView(mapView);

    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        mapContainer.removeView(mapView);
    }   

}

In theory, this should all work the same way as the new FrameLayout method first described. However, I get this every time:

02-24 18:01:28.139: E/AndroidRuntime(502): FATAL EXCEPTION: main
02-24 18:01:28.139: E/AndroidRuntime(502): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.mapfragment/com.example.mapfragment.MapScreenActivity}: java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
02-24 18:01:28.139: E/AndroidRuntime(502):  at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1647)
02-24 18:01:28.139: E/AndroidRuntime(502):  at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1663)
02-24 18:01:28.139: E/AndroidRuntime(502):  at android.app.ActivityThread.access$1500(ActivityThread.java:117)
02-24 18:01:28.139: E/AndroidRuntime(502):  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:931)
02-24 18:01:28.139: E/AndroidRuntime(502):  at android.os.Handler.dispatchMessage(Handler.java:99)
02-24 18:01:28.139: E/AndroidRuntime(502):  at android.os.Looper.loop(Looper.java:130)
02-24 18:01:28.139: E/AndroidRuntime(502):  at android.app.ActivityThread.main(ActivityThread.java:3683)
02-24 18:01:28.139: E/AndroidRuntime(502):  at java.lang.reflect.Method.invokeNative(Native Method)
02-24 18:01:28.139: E/AndroidRuntime(502):  at java.lang.reflect.Method.invoke(Method.java:507)
02-24 18:01:28.139: E/AndroidRuntime(502):  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
02-24 18:01:28.139: E/AndroidRuntime(502):  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
02-24 18:01:28.139: E/AndroidRuntime(502):  at dalvik.system.NativeStart.main(Native Method)
02-24 18:01:28.139: E/AndroidRuntime(502): Caused by: java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
02-24 18:01:28.139: E/AndroidRuntime(502):  at android.view.ViewGroup.addViewInner(ViewGroup.java:1976)
02-24 18:01:28.139: E/AndroidRuntime(502):  at android.view.ViewGroup.addView(ViewGroup.java:1871)
02-24 18:01:28.139: E/AndroidRuntime(502):  at android.view.ViewGroup.addView(ViewGroup.java:1828)
02-24 18:01:28.139: E/AndroidRuntime(502):  at android.view.ViewGroup.addView(ViewGroup.java:1808)
02-24 18:01:28.139: E/AndroidRuntime(502):  at android.support.v4.app.NoSaveStateFrameLayout.wrap(Unknown Source)
02-24 18:01:28.139: E/AndroidRuntime(502):  at android.support.v4.app.FragmentManagerImpl.moveToState(Unknown Source)
02-24 18:01:28.139: E/AndroidRuntime(502):  at android.support.v4.app.FragmentManagerImpl.moveToState(Unknown Source)
02-24 18:01:28.139: E/AndroidRuntime(502):  at android.support.v4.app.BackStackRecord.run(Unknown Source)
02-24 18:01:28.139: E/AndroidRuntime(502):  at android.support.v4.app.FragmentManagerImpl.execPendingActions(Unknown Source)
02-24 18:01:28.139: E/AndroidRuntime(502):  at android.support.v4.app.FragmentActivity.onStart(Unknown Source)
02-24 18:01:28.139: E/AndroidRuntime(502):  at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1129)
02-24 18:01:28.139: E/AndroidRuntime(502):  at android.app.Activity.performStart(Activity.java:3791)
02-24 18:01:28.139: E/AndroidRuntime(502):  at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1620)
02-24 18:01:28.139: E/AndroidRuntime(502):  ... 11 more

I've tried removing the the MapView from it's parent before returning from getMapView(), and that still crashes. I really don't understand why this approach doesn't work, any help at all would be appreciated.

Karakuri
  • 38,365
  • 12
  • 84
  • 104

4 Answers4

15

I'm not sure if this is the same problem as you were having, but it turns out that inflating is okay, so long as you don't attach to the root.

For example, you need to do something like this:

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    return inflater.inflate(R.id.my_layout, container, false);
}

If you fail to add that last false argument to the inflate() call, then you will get the IllegalStateException.

I think what is happening is that without the extra false argument, your inflated view tree is attached to the root view (container) and then when the Activity is started, the system attempts to add the view tree to the root again. Hence the error.

Scott W
  • 9,742
  • 2
  • 38
  • 53
  • 1
    Been a while since I revisited that project, but fragments were pretty new for me at the time, so I suspect this is actually the correct answer. – Karakuri Jul 06 '12 at 15:55
  • 1
    Thanks for this, didn't realise what was going on until you showed that last argument :) – lfxgroove Aug 11 '12 at 20:30
1

After trying a multitude of things, I ended up doing this:

The root view I returned in onCreateView() is created programmatically, not inflated. However, inflating other views and adding them as children to the programmatically-created root view doesn't seem to cause any problems.

Hat's off to anybody out there who can figure out what's behind this odd behavior. Hope this may be useful to others.

EDIT

It's been a while since I revisited this topic, but as I recall, this would not work for me...

public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle saved) {
    return inflater.inflate(R.layout.some_layout, container, false);
}

... whereas this would work for me...

private ViewGroup mapContainer;
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle saved) {
    mapContainer = new LinearLayout(getActivity());
    return mapContainer;
}

... and later in onActivityCreated() I would get a MapView from the activity and add it as a child of mapContainer. If I wanted other views (like maybe a header above the MapView), I could inflate them separately and add them to mapContainer, as this code snippet shows.

private ViewGroup mapContainer;
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle saved) {
    mapContainer = new LinearLayout(getActivity());
    mapContainer.setOrientation(LinearLayout.VERTICAL);
    View headerView = inflater.inflate(R.layout.some_layout, mapContainer, false);
    mapContainer.addView(headerView);
    return mapContainer;
}
Karakuri
  • 38,365
  • 12
  • 84
  • 104
0

[UPDATE] I wrote a little library, mashing all these LocalActivityManager solutions together: https://github.com/coreform/android-tandemactivities

[old post] You should

mapContainer.removeView(mapView);

before you

mapContainer.addView(mapView);

to avoid such an exception.

...as in literally the line before.

OK, followup after comments:

Try this:

if(mapView.getParent() != null) {
    mapContainer.addView(mapView);
}
straya
  • 5,002
  • 1
  • 28
  • 35
  • I've tried that as well and it doesn't work. Still gives the exact same error. – Karakuri Feb 25 '12 at 04:23
  • I've also tried variations on `((ViewGroup) mapView.getParent()).removeView(mapView)`, and none of that works either. – Karakuri Feb 25 '12 at 05:21
  • try finding out what parent your MapView is already attached to. if its the parent that you intent to add it to already, then it is added so you could handle the case with logic or a try/catch statement. I ue the solution I posted in comments on this thread: http://nextlevelandroid.com/?p=114 (comments by lindo) it works and doesn't require every friggen FragmentActivity to extend MapActivity! – straya Feb 25 '12 at 05:44
  • @straya : will you please share your code to understand easily. I have gone through many posts but couldn't find an easy solution. – gypsicoder May 21 '12 at 14:00
  • hey I'll try to put together a neat, generic example...but for now I'm busy attending google IO! XD – straya Jun 25 '12 at 21:16
0

This is what worked for me... Whenever I switched fragments I did

if (mapContainer.getChildAt(0) != null){
        mapContainer.removeViewAt(0);
}

I didn't put that in my onDestroy() or onPause() methods. I did that whenever I switched fragments. For some reason my fragment wasn't calling onPause().

In my onResume() method I did

mapContainer.addView(mapView);

and everything works for me. No more IllegalStateExceptions.

Kenny C
  • 2,279
  • 1
  • 19
  • 20