MapView in a Fragment (Honeycomb)
Asked Answered
O

12

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

Oneidaoneil answered 24/2, 2011 at 19:15 Comment(1)
I reported a feature request for this. Please star it. code.google.com/p/android/issues/detail?id=15347Voncile
I
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"/>
Ineducable answered 10/12, 2012 at 8:46 Comment(0)
W
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

Washboard answered 14/11, 2011 at 18:28 Comment(14)
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()Smoking
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 contentsTorry
@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.Megalopolis
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);Torry
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?Payday
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???Payday
How do you obtain contentView, I don't catch it ?Abstriction
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 intentAbstriction
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 youSmyrna
be careful, the LocalActivityManager is now deprecatedMabe
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.Potpourri
@ 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.Thetic
@Washboard the API LocalActivityManager is deprecated since API 13. Is it possible to do the same with newer classes? Thanks!Malvaceous
@Malvaceous Micaroni Lalli, this is actually a very popular question. And answer is no. Explanation: 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 code.google.com/p/android/issues/detail?id=15347.Washboard
N
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.

Nixon answered 28/7, 2011 at 11:36 Comment(4)
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.Airfield
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.Scleroma
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"?Maggs
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).Renaterenato
M
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());
    }
}
Meras answered 24/5, 2012 at 14:11 Comment(2)
But the only solution which avoids LocalActivityManager (as discussed here: code.google.com/p/android/issues/detail?id=15347) involves extending all FragmentActivities, which seems even less optimal.Anybody
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.Thetic
I
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"/>
Ineducable answered 10/12, 2012 at 8:46 Comment(0)
L
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" />
Luik answered 3/12, 2012 at 19:48 Comment(3)
Not exactly. Try using that in a FragmentPagerAdapter.Autotomy
If you are talking about the black view, there's a small hack to fix it #13838197Luik
thanks. I ended up seeing that later but I'm now using the first map api version with polaris.Autotomy
O
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.

Obvious answered 24/2, 2011 at 19:26 Comment(1)
Here's hoping Map API will be updated soon with fragments and the vector drawing!Enfeoff
O
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 ...

Oneidaoneil answered 25/2, 2011 at 17:25 Comment(1)
Thanks, making the Fragment managing Activity extend MapActivity worked perfectly for me.Scleroma
W
2

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

public class MapFragment : Fragment
{
    // FOLLOW https://mcmap.net/q/258600/-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();
    }
}
Wendall answered 2/10, 2012 at 1:45 Comment(0)
D
1

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

Danseuse answered 11/3, 2012 at 9:42 Comment(0)
S
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

Syphon answered 4/4, 2012 at 7:8 Comment(0)
V
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
Vacla answered 10/5, 2012 at 14:9 Comment(0)
T
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

Thetic answered 29/8, 2012 at 0:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.