MapView in a Fragment (Honeycomb)

AndroidGoogle MapsAndroid FragmentsAndroid MapsAndroid 3.0-Honeycomb

Android Problem Overview


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

Android Solutions


Solution 1 - Android

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

Solution 2 - Android

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.

Solution 3 - Android

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());
	}
}

Solution 4 - Android

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"/>

Solution 5 - Android

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" />

Solution 6 - Android

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.

Solution 7 - Android

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 ...

Solution 8 - Android

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();
	}
}

Solution 9 - Android

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

Solution 10 - Android

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

Solution 11 - Android

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

Solution 12 - Android

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionValentinView Question on Stackoverflow
Solution 1 - AndroidinazarukView Answer on Stackoverflow
Solution 2 - AndroidTomTascheView Answer on Stackoverflow
Solution 3 - AndroidzmicerView Answer on Stackoverflow
Solution 4 - AndroidPaul AnnekovView Answer on Stackoverflow
Solution 5 - AndroidMarceloView Answer on Stackoverflow
Solution 6 - AndroidOctavian A. DamieanView Answer on Stackoverflow
Solution 7 - AndroidValentinView Answer on Stackoverflow
Solution 8 - AndroidIan VinkView Answer on Stackoverflow
Solution 9 - AndroidSudhin PhilipView Answer on Stackoverflow
Solution 10 - AndroidcesardsView Answer on Stackoverflow
Solution 11 - AndroidJoe ZhangView Answer on Stackoverflow
Solution 12 - AndroidstrayaView Answer on Stackoverflow