Initialize MapFragment programmatically with Maps API v2

AndroidGoogle MapsAndroid Fragments

Android Problem Overview


I'm trying to add a MapFragment to my current Fragment. The use of nested fragments is restricted to FragmentTransactions, you can't use the xml tag in your layout. Also, I want it to be added to the main Fragment when the user presses a button. So, I'm creating the MapFragment programmatically with getInstance() when the user presses that button and adding it to the proper place. It is shown correctly, so far so good.

The problem is that after attaching the MapFragment I need to get a reference to GoogleMap to place a Marker, but the getMap() method returns null (as the fragment's onCreateView() hasn't been called yet).

I looked at the demo example code and I found the solution they use is initializing the MapFragment in onCreate() and getting the reference to GoogleMap in onResume(), after onCreateView() has been called.

I need to get the reference to GoogleMap right after the MapFragment initialization, because I want the users to be able to show or hide the map with a button. I know a possible solution would be to create the Map at the start as said above and just set it's visibility gone, but I want the map to be off by default so it doesn't take the user's bandwidth if they don't explicitly asked for it.

I tried with the MapsInitializer, but doesn't work either. I'm kind of stuck. Any ideas? Here is my testing code so far:

public class ParadaInfoFragment extends BaseDBFragment {
// BaseDBFragment is just a SherlockFragment with custom utility methods.

private static final String MAP_FRAGMENT_TAG = "map";
private GoogleMap mMap;
private SupportMapFragment mMapFragment;
private TextView mToggleMapa;
private boolean isMapVisible = false;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View v = inflater.inflate(R.layout.fragment_parada_info, container, false);
    mToggleMapa = (TextView) v.findViewById(R.id.parada_info_map_button);
    return v;
}

@Override
public void onStart() {
    super.onStart();
    mToggleMapa.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            if (!isMapVisible) {
                openMap();
            } else {
                closeMap();
            }
            isMapVisible = !isMapVisible;
        }
    });
}

private void openMap() {
    // Creates initial configuration for the map
    GoogleMapOptions options = new GoogleMapOptions().camera(CameraPosition.fromLatLngZoom(new LatLng(37.4005502611301, -5.98233461380005), 16))
            .compassEnabled(false).mapType(GoogleMap.MAP_TYPE_NORMAL).rotateGesturesEnabled(false).scrollGesturesEnabled(false).tiltGesturesEnabled(false)
            .zoomControlsEnabled(false).zoomGesturesEnabled(false);

    // Modified from the sample code:
    // It isn't possible to set a fragment's id programmatically so we set a
    // tag instead and search for it using that.
    mMapFragment = (SupportMapFragment) getChildFragmentManager().findFragmentByTag(MAP_FRAGMENT_TAG);

    // We only create a fragment if it doesn't already exist.
    if (mMapFragment == null) {
        // To programmatically add the map, we first create a
        // SupportMapFragment.
        mMapFragment = SupportMapFragment.newInstance(options);
        // Then we add it using a FragmentTransaction.
        FragmentTransaction fragmentTransaction = getChildFragmentManager().beginTransaction();
        fragmentTransaction.add(R.id.parada_info_map_container, mMapFragment, MAP_FRAGMENT_TAG);
        fragmentTransaction.commit();
    }
    // We can't be guaranteed that the map is available because Google Play
    // services might not be available.
    setUpMapIfNeeded(); //XXX Here, getMap() returns null so  the Marker can't be added
    // The map is shown with the previous options.
}

private void closeMap() {
    FragmentTransaction fragmentTransaction = getChildFragmentManager().beginTransaction();
    fragmentTransaction.remove(mMapFragment);
    fragmentTransaction.commit();
}

private void setUpMapIfNeeded() {
    // Do a null check to confirm that we have not already instantiated the
    // map.
    if (mMap == null) {
        // Try to obtain the map from the SupportMapFragment.
        mMap = mMapFragment.getMap();
        // Check if we were successful in obtaining the map.
        if (mMap != null) {
            mMap.addMarker(new MarkerOptions().position(new LatLng(37.4005502611301, -5.98233461380005)).title("Marker"));
        }
    }
}
}

Thanks

Android Solutions


Solution 1 - Android

The good AnderWebs gave me an answer in Google+ but he is too laz.... emm busy to write it here again, so here is the short version: Extend the MapFragment class and override the onCreateView() method. After this method is done we can get a non-null reference to que GoogleMap object.

This is my particular solution:

public class MiniMapFragment extends SupportMapFragment {
    private LatLng mPosFija;
    
    public MiniMapFragment() {
        super();
    }
    
    public static MiniMapFragment newInstance(LatLng posicion){
        MiniMapFragment frag = new MiniMapFragment();
        frag.mPosFija = posicion;
        return frag;
    }
    
    @Override
    public View onCreateView(LayoutInflater arg0, ViewGroup arg1, Bundle arg2) {
        View v = super.onCreateView(arg0, arg1, arg2);
        initMap();
        return v;
    }
    
    private void initMap(){
        UiSettings settings = getMap().getUiSettings();
        settings.setAllGesturesEnabled(false);
        settings.setMyLocationButtonEnabled(false);
        
        getMap().moveCamera(CameraUpdateFactory.newLatLngZoom(mPosFija,16));
        getMap().addMarker(new MarkerOptions().position(mPosFija).icon(BitmapDescriptorFactory.fromResource(R.drawable.marker)));
    }
}

Now in the previous Fragment class I do

mMapFragment = MiniMapFragment.newInstance(new LatLng(37.4005502611301, -5.98233461380005));

Maybe it's not perfect yet, because the screen blinks when showing the map. But not sure if the problem is because of this or something else.

Solution 2 - Android

Thanks, found this very helpful. Am posting my slightly modified solution, as it was cleaner for me to tell the parent Fragment when the map was ready. This method also works with a saveInstanceState / restoreInstanceState cycle.

public class CustomMapFragment extends SupportMapFragment {

    private static final String LOG_TAG = "CustomMapFragment";

    public CustomMapFragment() {
        super();

    }

    public static CustomMapFragment newInstance() {
        CustomMapFragment fragment = new CustomMapFragment();
        return fragment;
    }

    @Override
    public View onCreateView(LayoutInflater arg0, ViewGroup arg1, Bundle arg2) {
        View v = super.onCreateView(arg0, arg1, arg2);
        Fragment fragment = getParentFragment();
        if (fragment != null && fragment instanceof OnMapReadyListener) {
            ((OnMapReadyListener) fragment).onMapReady();
        }
        return v;
    }



    /**
     * Listener interface to tell when the map is ready
     */
    public static interface OnMapReadyListener {
        
        void onMapReady();
    }
}

To use as a nested Fragment:-

public class ParentFragment extends Fragment implements OnMapReadyListener {

    ...

    mMapFragment = CustomMapFragment.newInstance();
    getChildFragmentManager().beginTransaction().replace(R.id.mapContainer, mMapFragment).commit();

    @Override
    public void onMapReady() {
        mMap = mMapFragment.getMap();
    }
    ...
}

Hope it helps someone.

Solution 3 - Android

Here's my solution to this, I took inspiration from the code previously posted and cleaned it up. I also added the static methods with and without the GoogleMapOptions parameters.

public class GoogleMapFragment extends SupportMapFragment {

	private static final String SUPPORT_MAP_BUNDLE_KEY = "MapOptions";
	
	public static interface OnGoogleMapFragmentListener {
		void onMapReady(GoogleMap map);
	}

	public static GoogleMapFragment newInstance() {
		return new GoogleMapFragment();
	}
	
	public static GoogleMapFragment newInstance(GoogleMapOptions options) {
		Bundle arguments = new Bundle();
		arguments.putParcelable(SUPPORT_MAP_BUNDLE_KEY, options);

		GoogleMapFragment fragment = new GoogleMapFragment();
		fragment.setArguments(arguments);
		return fragment;
	}
	
	@Override
	public void onAttach(Activity activity) {
		super.onAttach(activity);
		try {
            mCallback = (OnGoogleMapFragmentListener) getActivity();
        } catch (ClassCastException e) {
            throw new ClassCastException(getActivity().getClass().getName() + " must implement OnGoogleMapFragmentListener");
        }
	}

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
		View view = super.onCreateView(inflater, container, savedInstanceState);
		if (mCallback != null) {
			mCallback.onMapReady(getMap());
		}
		return view;
	}

    private OnGoogleMapFragmentListener mCallback;
}

The usage pattern is as follows:

public class MyMapActivity implements OnGoogleMapFragmentListener {

    ...

   	@Override
	public void onMapReady(GoogleMap map) {
        mUIGoogleMap = map;

        ...

    }

    ...

    private GoogleMap mUIGoogleMap;
}

Solution 4 - Android

No need to cutomize SupportMapFragment you can do this directly by using following piece of code,

FragmentManager fm = getSupportFragmentManager(); // getChildFragmentManager inside fragments.
CameraPosition cp = new CameraPosition.Builder()
                    .target(initialLatLng) // your initial co-ordinates here. like, LatLng initialLatLng
                    .zoom(zoom_level)
                    .build();
SupportMapFragment mapFragment = SupportMapFragment.newInstance(new GoogleMapOptions().camera(cp));
fm.beginTransaction().replace(R.id.rl_map, mapFragment).commit();

Add this piece of code for layout

<RelativeLayout
       android:id="@+id/rl_map"
       android:layout_width="fill_parent"
       android:layout_height="fill_parent" />

This will load GoogleMap at particular Location directly i.e, initialLatLng.

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
QuestionSloyView Question on Stackoverflow
Solution 1 - AndroidSloyView Answer on Stackoverflow
Solution 2 - AndroidRyanView Answer on Stackoverflow
Solution 3 - AndroidMatteo GiacconeView Answer on Stackoverflow
Solution 4 - AndroidJaydipsinh ZalaView Answer on Stackoverflow