81

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

Programmer Bruce
  • 64,977
  • 7
  • 99
  • 97
Valentin
  • 2,066
  • 3
  • 18
  • 14

12 Answers12

87

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

inazaruk
  • 74,247
  • 24
  • 188
  • 156
  • 4
    If you then want the MapActivity to be able to comm with the parent Activity (which is easy with a Fragment) then you can use Activity.getParent() – Dori Dec 14 '11 at 14:18
  • 3
    You don't really need to use a TabHost, you can also just use a LocalActivityManager and attach the returned window to your fragment's contents – ChristophK Feb 10 '12 at 10:20
  • @christophk, could you please elaborate on what you mean by "attach the returned window to your fragment's content"? I don't really get it but it seems quite interesting. – Arnaud Feb 29 '12 at 11:11
  • 4
    In case someone else is wondering, here is the code: `Intent i = new Intent(getActivity().getParent(), MyMapActivity.class); Window w = localActivityManager.startActivity("tag", i); currentView=w.getDecorView(); currentView.setVisibility(View.VISIBLE); currentView.setFocusableInTouchMode(true); ((ViewGroup) currentView).setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); this.contentView.addView(currentView);` – ChristophK Feb 29 '12 at 16:16
  • i have one fragment containing a list of title and a fragment containing the map view. how can i refresh the map view on click on one element of fragmentTilte? – haythem souissi Mar 29 '12 at 12:05
  • can we communicate between the two mapView in this project. for example if i zoom in the first map, the second will zoom automatically her position??? – haythem souissi Mar 30 '12 at 13:02
  • How do you obtain contentView, I don't catch it ? – Snicolas Apr 12 '12 at 20:41
  • In case someone is wondering here is what you have to add into LocalActivityManagerFragment (instead of onCreate) : onCreateView, create a ViewGroup (like a linear layout) and you will return it. This is your content view. Include all previous code of onCreate before the creation of the intent – Snicolas Apr 12 '12 at 20:53
  • I'm not sure I totally understand the alternate option explained by ChristophK and Snicolas... Would it be possible to add some code outlining the changes (i.e., using the LocalActivityManager instead of a TabHost)? Thank you – Kyle May 02 '12 at 17:31
  • 8
    be careful, the LocalActivityManager is now deprecated – louiscoquio May 29 '12 at 12:50
  • 4
    It does work well for me, using tabs and fragments. For those asking about the explanation, I would rather see the project and its code, it's easy to understand. And yes, `LocalActivityManager` is deprecated, but this is just a hack, this issue shall be solved properly when they decide to include the appropriate support for Maps within Fragments. – mdelolmo Aug 07 '12 at 12:13
  • @ haythem souissi, re: can zooming on one mapview cause the other one to also zoom?: Yes, it should work if you call invalidate on the 2nd mapview. – straya Aug 29 '12 at 00:11
  • @inazaruk the API LocalActivityManager is deprecated since API 13. Is it possible to do the same with newer classes? Thanks! – Felipe Nov 28 '12 at 17:59
  • 1
    @Felipe Micaroni Lalli, this is actually a very popular question. And answer is no. Explanation: https://github.com/inazaruk/map-fragment/issues/2. There is no other way to implement this or I simply am not aware of it. Also please read comments above and here http://code.google.com/p/android/issues/detail?id=15347. – inazaruk Nov 28 '12 at 19:29
23

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.

TomTasche
  • 5,448
  • 7
  • 41
  • 67
  • 6
    This also means your app will not be up to date with the latest support library. While the android-support-v4-googlemaps has been updated a number of times, it's currently 2 versions behind. I agree with others that this modified library is a bigger hack than using the deprecated LocalActivityManager. – Stan Kurdziel Aug 02 '12 at 02:08
  • 1
    https://github.com/petedoyle/android-support-v4-googlemaps is up to date now (r10 as of this message). I've also refactored things so it'll be super easy to keep up to date in the future. – Pete Doyle Sep 25 '12 at 19:43
  • I have one question. If application is already deployed and installed, what's the disadvantage of "not being up to date with latest support library"? – hendrix Oct 23 '12 at 16:01
  • The primary problem with this approach, IMO, is that you won't be able to deploy your app with optional Maps support. Since all FragmentActivities extend MapActivity, it will simply crash on devices without Google APIs (e.g. Kindle Fire). – Paul Lammertsma Oct 29 '12 at 12:41
13

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());
    }
}
zmicer
  • 172
  • 1
  • 4
  • 2
    But the only solution which avoids `LocalActivityManager` (as discussed here: http://code.google.com/p/android/issues/detail?id=15347) involves extending *all* `FragmentActivities`, which seems even less optimal. – Estel Jul 19 '12 at 09:19
  • LocalActivityManager is more deprecated because ActivityGroup is deprecated, still useful for this situation and possibly Google will think twice about ousting it in future releases if people are using it to solve their shortcomings. – straya Aug 29 '12 at 00:07
8

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"/>
Paul Annekov
  • 3,193
  • 3
  • 19
  • 31
4

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" />
Marcelo
  • 1,471
  • 3
  • 19
  • 22
2

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();
    }
}
Ian Vink
  • 66,960
  • 104
  • 341
  • 555
2

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.

Octavian Helm
  • 39,405
  • 19
  • 98
  • 102
2

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

Valentin
  • 2,066
  • 3
  • 18
  • 14
1

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

cesards
  • 15,882
  • 11
  • 70
  • 65
1

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
Michael J. Barber
  • 24,518
  • 9
  • 68
  • 88
Joe Zhang
  • 11
  • 1
1

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

Sudhin Philip
  • 644
  • 8
  • 15
0

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

straya
  • 5,002
  • 1
  • 28
  • 35