MapView in a Fragment (Honeycomb)

now that the final SDK is out with google apis - what is the best way to create a Fragment with a MapView? MapView needs a MapActivity to work right.

Having the Activity managing the Fragments inherit from MapActivity (bad solution because it goes against the idea that Fragments are self contained) and use a regular xml based layout does not work. I get a NullPointerException in MapActivity.setupMapView():

E/AndroidRuntime(  597): Caused by: java.lang.NullPointerException
E/AndroidRuntime(  597):    at com.google.android.maps.MapActivity.setupMapView(MapActivity.java:400)
E/AndroidRuntime(  597):    at com.google.android.maps.MapView.(MapView.java:289)
E/AndroidRuntime(  597):    at com.google.android.maps.MapView.(MapView.java:264)
E/AndroidRuntime(  597):    at com.google.android.maps.MapView.(MapView.java:247)

My second idea was to create the MapView programmatically and pass the associated activity (via getActivity()) as Context to the MapView constructor. Does not work:

E/AndroidRuntime(  834): Caused by: java.lang.IllegalArgumentException: MapViews can only be created inside instances of MapActivity.
E/AndroidRuntime(  834):    at com.google.android.maps.MapView.(MapView.java:291)
E/AndroidRuntime(  834):    at com.google.android.maps.MapView.(MapView.java:235)
E/AndroidRuntime(  834):    at de.foo.FinderMapFragment.onCreateView(FinderMapFragment.java:225)
E/AndroidRuntime(  834):    at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:708)
E/AndroidRuntime(  834):    at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:900)
E/AndroidRuntime(  834):    at android.app.FragmentManagerImpl.addFragment(FragmentManager.java:978)
E/AndroidRuntime(  834):    at android.app.Activity.onCreateView(Activity.java:4090)
E/AndroidRuntime(  834):    at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:664)

Really there should be something like MapFragment that takes care of the background threads MapView needs I guess... So what is the current best practice to do this?

Thanks and regards from Germany, Valentin

45266 次浏览

The Google Maps API is not part of the AOSP. As long as no Googler responds it is barely possible to tell if there will be a MapFragment in the future.

A possible limited alternative is to use a WebViewFragment and abuse it to load up a custom maps.google.com URL.

Hm too bad that Google has not responded yet. FWIW if you really need to do this I found no other way than:

Have the Tab Managing Activity inherit from MapActivity, create the MapView in there programmatically, have the mapfragment.xml contain a ViewGroup and add the MapView to the ViewGroup using

((ViewGroup) getFragmentManager().findFragmentById(R.id.finder_map_fragment).getView()).addView(mapView);;

Clearly this goes strongly against the idea that fragments are ment to be self-contained but ...

As discussed at Google Groups, Peter Doyle built a custom compatibility library supporting Google Maps too. android-support-v4-googlemaps

However, there's a downside too:

Currently, one downside is that ALL classes extending FragmentActivity are MapActivitys. Its possible to make a separate class (i.e. FragmentMapActivity), but it requires some refactoring of the FragmentActivity code.

I've managed to resolve this by using TabHost in fragment.

Here is the idea (briefly):

  1. MainFragmentActivity extends FragmentActivity (from support library) and has MapFragment.

  2. MyMapActivity extends MapActivity and contain MapView.

  3. LocalActivityManagerFragment hosts LocalActivityManager

  4. MapFragment extends LocalActivityManagerFragment.

  5. And LocalActivityManager contains MyMapActivity activity in it.

Example implementation: https://github.com/inazaruk/map-fragment.


enter image description here

This solves my issue in adding MapView in Fragments. https://github.com/petedoyle/android-support-v4-googlemaps

With the new version of ABS 4.0, there is no suppport for MapFragmentActivity, here is a good solution for having a mapview in a Fragment!

https://xrigau.wordpress.com/2012/03/22/howto-actionbarsherlock-mapfragment-listfragment/#comment-21

May I get the solution:

  1. create class TempFragmentActivity extends MapActivity
  2. there is a MapView object inside TempFragmentActivity(like normal define in xml)
  3. remove this MapView object form parent(LinearLayout)(void later exception)
  4. keep this MapView object in somewhere(ex: static member of TempFragmentActivity)
  5. in your Fragment , add this MapView object using code(do not define in xml) into some LinearLayout

Just to clarify the answer. I tried the approach suggested by inazaruk and ChristophK. Actually you can run any activity in a fragment - not just google maps. Here is the code which implements google map activity as a fragment thanks to inazaruk and ChristophK.

import com.actionbarsherlock.app.SherlockFragment;
import android.view.Window;


import android.app.LocalActivityManager;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;


public class MapFragment extends SherlockFragment {
private static final String KEY_STATE_BUNDLE = "localActivityManagerState";


private LocalActivityManager mLocalActivityManager;


protected LocalActivityManager getLocalActivityManager() {
return mLocalActivityManager;
}


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


Bundle state = null;
if (savedInstanceState != null) {
state = savedInstanceState.getBundle(KEY_STATE_BUNDLE);
}


mLocalActivityManager = new LocalActivityManager(getActivity(), true);
mLocalActivityManager.dispatchCreate(state);
}


public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
//This is where you specify you activity class
Intent i = new Intent(getActivity(), GMapActivity.class);
Window w = mLocalActivityManager.startActivity("tag", i);
View currentView=w.getDecorView();
currentView.setVisibility(View.VISIBLE);
currentView.setFocusableInTouchMode(true);
((ViewGroup) currentView).setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
return currentView;
}


@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBundle(KEY_STATE_BUNDLE,
mLocalActivityManager.saveInstanceState());
}


@Override
public void onResume() {
super.onResume();
mLocalActivityManager.dispatchResume();
}


@Override
public void onPause() {
super.onPause();
mLocalActivityManager.dispatchPause(getActivity().isFinishing());
}


@Override
public void onStop() {
super.onStop();
mLocalActivityManager.dispatchStop();
}


@Override
public void onDestroy() {
super.onDestroy();
mLocalActivityManager.dispatchDestroy(getActivity().isFinishing());
}
}

I wrote a little library, mashing up the LocalActivityManager-based solutions to the MapFragment problem (also includes an example app showing various usage situations):

https://github.com/coreform/android-tandemactivities

Here's a MonoDroid (Mono for Android) version of a very-simplified MapFragment:

public class MapFragment : Fragment
{
// FOLLOW http://stackoverflow.com/questions/5109336/mapview-in-a-fragment-honeycomb
private static  String KEY_STATE_BUNDLE = "localActivityManagerState";


public override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);


Bundle state = null;
if (savedInstanceState != null) {
state = savedInstanceState.GetBundle(KEY_STATE_BUNDLE);
}
mLocalActivityManager = new LocalActivityManager(Activity, true);
mLocalActivityManager.DispatchCreate(state);
}


public override Android.Views.View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
//This is where you specify you activity class
Intent i = new Intent(Activity, typeof(SteamLocationMapActivity));
Window w = mLocalActivityManager.StartActivity("tag", i);
View currentView=w.DecorView;
currentView.Visibility = ViewStates.Visible;
currentView.FocusableInTouchMode = true;
((ViewGroup) currentView).DescendantFocusability = DescendantFocusability.AfterDescendants;
return currentView;
}


private LocalActivityManager mLocalActivityManager;
protected LocalActivityManager GetLocalActivityManager() {
return mLocalActivityManager;
}




public override void OnSaveInstanceState(Bundle outState)
{
base.OnSaveInstanceState(outState);
outState.PutBundle(KEY_STATE_BUNDLE,mLocalActivityManager.SaveInstanceState());
}


public override void OnResume()
{
base.OnResume();
mLocalActivityManager.DispatchResume();


}


public override void OnPause()
{
base.OnPause();
mLocalActivityManager.DispatchPause(Activity.IsFinishing);
}


public override void OnStop()
{
base.OnStop();
mLocalActivityManager.DispatchStop();
}
}

Great news from Google on this. They are releasing today a new Google Maps API, with indoor maps and MapFragment.

With this new API, adding a map to your Activity is as simple as:


<fragment
android:id="@+id/map"
android:layout_width="match_parent"
android:layout_height="match_parent"
class="com.google.android.gms.maps.MapFragment" />

As of 03.12.2012 Google released Google Maps Android API v2. Now you can forget about these problems. https://developers.google.com/maps/documentation/android/

Example using new API - https://developers.google.com/maps/documentation/android/start#add_a_map

This API will work for at least Android API 8, so use it ;).

So now you can simply use "com.google.android.gms.maps.MapFragment" fragment class. It will display the map in your Activity. Layout example from the link above:

<?xml version="1.0" encoding="utf-8"?>
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/map"
android:layout_width="match_parent"
android:layout_height="match_parent"
class="com.google.android.gms.maps.MapFragment"/>