So I have the following things that are working properly:
I have a map fragment and I have markers on my map. Using .setRetainInstance on my map fragment does exactly what I want, on rotate it keeps the user zoomed position and keeps the markers on their place. The only thing that I want to do now is to make the initial screen of the app to move the camera to a exact location and zoom level. I am doing that with the initialLocation method which if added in the onMapReady does what its supposed to. The problem is that once I add this method inside onMapReady the setRetainInstance is not working anymore, on each rotation the map resets to the initialLocation position. As u will probably realize from my code I am just learning this and I have read a lot of tutorials, but I cannot manage to make it right. Here is part of the code so you can have an idea of what I am talking about. I guess I have to add some sort of conditions in order for this to work. Any suggestions will be appreciated.
private static final double
TOULOUSE_LAT = 43.604346,
TOULOUSE_LNG = 1.443760;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
SupportMapFragment mapFragment = (SupportMapFragment)getSupportFragmentManager().findFragmentById(R.id.map);
mapFragment.getMapAsync(this);
if (savedInstanceState == null){
mapTypeSelected = GoogleMap.MAP_TYPE_NORMAL;
mapFragment.setRetainInstance(true);
} else {
mapTypeSelected = savedInstanceState.getInt("the_map_type", GoogleMap.MAP_TYPE_NORMAL);
}
#Override
public void onMapReady(GoogleMap googleMap) {
mMap = googleMap;
initialLocation(TOULOUSE_LAT,TOULOUSE_LNG, 12);
mMap.setMapType(mapTypeSelected);
addMarkers2Map();// method for adding markers and a lot of other stuff...
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("the_map_type", mapTypeSelected);
}
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
savedInstanceState.get("the_map_type");
}
private void initialLocation(double lat, double lng, float zoom){
LatLng latLng = new LatLng(lat, lng );
CameraUpdate update = CameraUpdateFactory.newLatLngZoom(latLng, zoom);
mMap.moveCamera(update);
}
}
Try adding a member variable similar to this:
Boolean mSetCameraPosition;
Then in onCreate() set the value of mSetCameraPosition before calling getMapAsync():
if (savedInstanceState == null) {
mapTypeSelected = GoogleMap.MAP_TYPE_NORMAL;
mapFragment.setRetainInstance(true);
mSetCameraPosition = true;
}
else {
mapTypeSelected = savedInstanceState.getInt("the_map_type", GoogleMap.MAP_TYPE_NORMAL);
mSetCameraPosition = false;
}
mapFragment.getMapAsync(this);
In onMapReady() use mSetCameraPosition:
#Override
public void onMapReady(GoogleMap googleMap) {
mMap = googleMap;
if (mSetCameraPosition) {
initialLocation(TOULOUSE_LAT,TOULOUSE_LNG, 12);
}
mMap.setMapType(mapTypeSelected);
addMarkers2Map(); // method for adding markers and a lot of other stuff...
}
Related
I'm trying to post a marker onto a map from a separate fragment. So when you tap a button, the fragment manager will take you to the map fragment and then show you your newly placed marker. But I'm having some (nullPointer) issues.
I'm getting a null pointer because I'm trying to add a marker before the map loads. I've tried some methods to find a way to add the marker when the map is ready but I couldn't get anything to work.
Is there a clean solution to adding a marker when the map is ready?
Also, here's the error that comes up:
java.lang.NullPointerException: Attempt to invoke virtual method 'com.google.android.gms.maps.model.Marker com.google.android.gms.maps.GoogleMap.addMarker(com.google.android.gms.maps.model.MarkerOptions)' on a null object reference
EDIT: The way I'm getting the marker information (LatLng) is with a method I found on stackoverflow. What it does is, it takes the address from an EditText and retrieves the Latitude and Longitude points from it. I use that to store it into 'marker' and 'point'. The method is found below...
UPDATE: It no longer gives me a null exception. The MapFragment loads but there's no marker to be seen.
In my PostFragment class:
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
setUpSpinners();
Button button = (Button) getActivity().findViewById(R.id.postButton);
button.setOnClickListener(
new Button.OnClickListener() {
public void onClick(View v) {
EditText editEvent = (EditText) getActivity().findViewById(R.id.map_location);
point = getLocationFromAddress(getActivity()
.getApplicationContext(), editEvent.getText().toString());
marker = new MarkerOptions().position(
new LatLng(point.latitude, point.longitude)).title("New Marker");
FragmentManager fm = getFragmentManager();
fm.beginTransaction().replace(R.id.content_frag, new GoogleMapFragment()).commit();
}
}
);
}
public void addThisMarker(GoogleMap googleMap) {
googleMap.addMarker(marker
.position(point)
.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_YELLOW)));
}
And in my MapFragment class:
#Override
public void onMapReady(GoogleMap googleMap) {
globalMap = googleMap;
setUpMap(); //just enabling some map properties here
PostFragment x = new PostFragment();
x.addThisMarker(globalMap);
}
**And this is the method I'm using, in PostFragment, to retrieve LatLng from an address
public LatLng getLocationFromAddress(Context context, String strAddress) {
Geocoder coder = new Geocoder(context);
List<Address> address;
LatLng p1 = null;
try {
address = coder.getFromLocationName(strAddress, 5);
if (address == null) {
return null;
}
Address location = address.get(0);
location.getLatitude();
location.getLongitude();
p1 = new LatLng(location.getLatitude(), location.getLongitude());
} catch (Exception ex) {
ex.printStackTrace();
}
return p1;
}
You can do it like this:
public class MapsActivity extends FragmentActivity implements OnMapReadyCallback {
#Override
protected void onCreate(Bundle savedInstanceState) {
// ...
MapFragment fMap = (MapFragment) getFragmentManager().findFragmentById(R.id.map);
fmap.getMapAsync(this);
}
#Override
public void onMapReady(final GoogleMap googleMap) {
map = googleMap;
// Draw your marker here
}
}
Guys I'm implementing google maps in my android app and instread of creating a marker i've placed a marker image in the middle of map. Now I want that whenever user drags the map i get the location at the centre of the map(where i've placed my image look like a marker).
My map activity is :
public class MapActivity extends FragmentActivity implements LocationListener {
private GoogleMap mMap; // Might be null if Google Play services APK is not available.
TextView Title;
FrameLayout goback;
Location myLocation;
LocationManager locationManager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_map);
setUpMapIfNeeded();
SupportMapFragment supportMapFragment =
(SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map);
mMap = supportMapFragment.getMap();
LocationManager locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
Criteria criteria = new Criteria();
String bestProvider = locationManager.getBestProvider(criteria, true);
Location location = locationManager.getLastKnownLocation(bestProvider);
if (location != null) {
onLocationChanged(location);
}
locationManager.requestLocationUpdates(bestProvider, 20000, 0, this);
//try
Title=(TextView)findViewById(R.id.map_title);
Title.setText(getIntent().getExtras().getString("Header"));
goback=(FrameLayout)findViewById(R.id.frame_layout);
setUpMapIfNeeded();
// mMap.setMyLocationEnabled(true);
}
#Override
protected void onResume() {
super.onResume();
setUpMapIfNeeded();
}
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 = ((SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map))
.getMap();
// Check if we were successful in obtaining the map.
if (mMap != null) {
setUpMap();
}
}
}
private void setUpMap() {
}
#Override
public void onLocationChanged(Location location) {
double latitude = location.getLatitude();
double longitude = location.getLongitude();
LatLng latLng = new LatLng(latitude, longitude);
BitmapDescriptor icon = BitmapDescriptorFactory.fromResource(R.drawable.marker);
mMap.moveCamera(CameraUpdateFactory.newLatLng(latLng));
mMap.animateCamera(CameraUpdateFactory.zoomTo(5));
CameraPosition ll=mMap.getCameraPosition();
Toast.makeText(getApplicationContext(),""+ll,Toast.LENGTH_LONG).show();
}
#Override
public void onStatusChanged(String s, int i, Bundle bundle) {
}
#Override
public void onProviderEnabled(String s) {
}
#Override
public void onProviderDisabled(String s) {
}
please help me in doing so, thank you :)
First you can get referance of your map container, and calculate center point by dividing 2 width and height.
View containerView=findViewById(R.id.mapContainer);
LatLng centerPoint= this.map.getProjection().fromScreenLocation(new Point(((int)containerView.getWidth/2),((int)(containerView.getHeight/2)));
you can get the center this way:
mMap.getCameraPosition().target
where mMap is the GoogleMap instance from your activity. This will return a LatLng object which basically represents the center of the map. Note that the GeoPoint class is not anymore available.
According to http://developer.android.com/reference/com/google/android/gms/maps/model/CameraPosition.html
target is "The location that the camera is pointing at." (tested it with the sample code and it worked ok for me)
Let me know if this helped you.
Cheers!
You can use this method
MapView.getProjection().fromPixels(x, y)
Where x is half your map width and y is half the height. This should return you a coordinates object which in turn will give you your longitude and latitude of the center of your map
More information on it can be seen here
Is it possible to change the zoom level as soon as the map is ready? When I open the app it shows the map and the blue dot for my location. However, the zoom level is the default 3. How can I change this? I know how to do it when the 'MyLocationButton'is clicked but not when the app starts.
This is my class
public class MainPhoneActivity extends FragmentActivity implements ConnectionCallbacks, OnConnectionFailedListener, LocationSource, LocationListener, OnMyLocationButtonClickListener, OnMapReadyCallback{
private GoogleApiClient mGoogleApiClient;
private OnLocationChangedListener mMapLocationListener = null;
// location accuracy settings
private static final LocationRequest REQUEST = LocationRequest.create()
.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_phone);
SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
.findFragmentById(R.id.mapView);
mapFragment.getMapAsync(this);
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.addApi(LocationServices.API)
.build();
}
#Override
protected void onResume() {
super.onResume();
mGoogleApiClient.connect();
}
#Override
public void onPause() {
super.onPause();
mGoogleApiClient.disconnect();
}
#Override
public void onMapReady(GoogleMap map) {
map.setLocationSource(this);
map.setMyLocationEnabled(true);
map.setOnMyLocationButtonClickListener(this);
}
public void showMyLocation(View view) {
if (mGoogleApiClient.isConnected()) {
String msg = "Location = "
+ LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT).show();
}
}
#Override
public void onLocationChanged(Location location) {
if (mMapLocationListener != null) {
mMapLocationListener.onLocationChanged(location);
}
}
#Override
public void onConnected(Bundle connectionHint) {
LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, REQUEST,this);
}
#Override
public void onConnectionSuspended(int cause) {
// Do nothing
}
#Override
public void onConnectionFailed(ConnectionResult result) {
// Do nothing
}
#Override
public boolean onMyLocationButtonClick() {
return false;
}
#Override
public void activate(OnLocationChangedListener onLocationChangedListener) {
mMapLocationListener = onLocationChangedListener;
}
#Override
public void deactivate() {
mMapLocationListener = null;
}
}
You can set zoom level like following:
map.animateCamera(CameraUpdateFactory.newLatLngZoom(point,15));
Where point is your LatLng position. In this way you can center the map in that point with given zoom level.
Complete method:
#Override
public void onMapReady(GoogleMap map) {
map.setLocationSource(this);
map.setMyLocationEnabled(true);
map.setOnMyLocationButtonClickListener(this);
//newLatLngZoom(LatLng , ZoomLevel) -> choose your zoom level
// and change my 'point' with yours
map.animateCamera(CameraUpdateFactory.newLatLngZoom(point,15));
}
EDIT:
If you want to get the dot coords, you can try this:
Location loc = map.getMyLocation();
LatLng point = new LatLng(loc.getlatitude() , loc.getLongitude());
and use that point as center.
Complete method:
#Override
public void onMapReady(GoogleMap map) {
map.setLocationSource(this);
map.setMyLocationEnabled(true);
map.setOnMyLocationButtonClickListener(this);
Location loc = map.getMyLocation();
if(loc != null){
LatLng point = new LatLng(loc.getLatitude() , loc.getLongitude());
map.animateCamera(CameraUpdateFactory.newLatLngZoom(point,15));
}
}
This could cause a NullPointerException beacuse loc could be null.
OTHER SOLUTION
If you want to get only first time the coordinates, you should work in onLocationChanged, using a boolean variable to set first call.
Declare it CRDS_CALL = false;
#Override
public void onLocationChanged(Location location) {
if (mMapLocationListener != null) {
mMapLocationListener.onLocationChanged(location);
if(!CRDS_CALL){
LatLng point = new LatLng(location.getLatitude(), location.getLognitude());
map.animateCamera(CameraUpdateFactory.newLatLngZoom(point,15));
CRDS_CALL = true;
}
}
}
In this answer i use map, but you have to use your mapFragment, but if you want to use it in other methods over onCreate, you have to declare outside of it.
Add this just before the onCreate
SupportMapFragment mapFragment;
And inside it, use it like follwing:
mapFragment = (SupportMapFragment) getSupportFragmentManager()
.findFragmentById(R.id.mapView);
mapFragment.getMapAsync(this);
In that way, you can use mapFragment in other methods
Solution
The solution was to do everything #MikeKeepsOnShine said but remove the
Location loc = map.getMyLocation();
LatLng point = new LatLng(loc.getLatitude() , loc.getLongitude());
map.animateCamera(CameraUpdateFactory.newLatLngZoom(point,15));
part from onMapReady. Works absolutely perfectly now!
You should use this. And you should put this request in onMapReady() callback. Something like this:
#Override
public void onMapReady(GoogleMap map) {
map.setLocationSource(this);
map.setMyLocationEnabled(true);
map.setOnMyLocationButtonClickListener(this);
map.animateCamera(CameraUpdateFactory.zoomTo(14), 2000, null);
}
you have already done the new "MapAsync" way so you are one step forward to the result.
In order to zoom, you can use the animateCamera method for googleMap object to zoom to a specific location:
https://developers.google.com/android/reference/com/google/android/gms/maps/GoogleMap.html#animateCamera(com.google.android.gms.maps.CameraUpdate)
for example, a zoomin:
map.animateCamera(CameraUpdateFactory.zoomIn());
or if you already know "where":
map.animateCamera(CameraUpdateFactory.newLatLngZoom(knownlocation,17));
and you will be very close to the location.
If you need to do at the first "locationUpdate" you should keep a flag to false and set to true the first time you receive a location, at that time you perform the zoom to location.
EDIT: If you want to zoom only the first time, where you receive location updates (which is not really clear from your code), you can do, assuming you have a class variable like:
private boolean FIRST_TIME = true;
the following code:
if(FIRST_TIME){
myMap.animateCamera(CameraUpdateFactory.newLatLngZoom(location,17));
FIRST_TIME = false;
}
The best option is to remove the listener but it seems you use location updates as "blue dot" updates.
What worked for me was:
mMap.setMinZoomPreference(6.0f);
mMap.setMaxZoomPreference(20.0f);
You can modify the zoom preference values to your taste.
I'm using the solution here to add markers to a map in a Fragment and can't figure out why I can only add one marker.
I'm sure I'm missing something when I build the camera position, which is getting called twice in my test code. (See addLocationToMap below).
I am retrieving an ArrayList of geocoordinates on the main thread and want to loop through the list in the map tab calling addLocationToMap on each entry.
Here's my code:
public class RentalMapFragment extends Fragment {
MapView mMapView;
private GoogleMap googleMap;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// inflate and return the layout
View v = inflater.inflate(R.layout.fragment_map, container, false);
mMapView = (MapView) v.findViewById(R.id.mapView);
mMapView.onCreate(savedInstanceState);
mMapView.onResume();// needed to get the map to display immediately
try {
MapsInitializer.initialize(getActivity().getApplicationContext());
} catch (Exception e) {
e.printStackTrace();
}
googleMap = mMapView.getMap();
int level = 12;
addLocationToMap(17.385044, 78.486679, "Marker 2", level);
addLocationToMap(17.385044, 78.486671, "Marker 1", level);
// Perform any camera updates here
CameraPosition cameraPosition = new CameraPosition.Builder().target(new LatLng(17.385000, 78.486600)).zoom(level).build();
googleMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));
return v;
}
private void addLocationToMap(double latitude, double longitude, String markerTitle, int level) {
// Create lat/lon
LatLng latLng = new LatLng(latitude, longitude);
// create marker
MarkerOptions marker = new MarkerOptions().position(latLng).title(markerTitle);
// Changing marker icon
marker.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_ROSE));
// adding marker
googleMap.addMarker(marker);
}
#Override
public void onResume() {
super.onResume();
mMapView.onResume();
}
#Override
public void onPause() {
super.onPause();
mMapView.onPause();
}
#Override
public void onDestroy() {
super.onDestroy();
mMapView.onDestroy();
}
#Override
public void onLowMemory() {
super.onLowMemory();
mMapView.onLowMemory();
}
}
It turns out that I should not be creating the camera position after placing each marker. Rather, after adding all my markers I need to choose a postion for the camera to view the markers from.
I'm updating my question to demonstrate this.
I have a GoogleMap which displays fine (within a SupportMapFragment) and uses the GoogleMapOptions for the target camera location. However, I am unable to add markers/polylines to the GoogleMap. Here is the method for creating the map:
private void createMap(List<LatLng> latLngs) {
if(map == null) {
GoogleMapOptions options = new GoogleMapOptions();
mapFragment = SupportMapFragment.newInstance(options);
map = mapFragment.getMap();
float zoom = 13;
CameraPosition cameraP = new CameraPosition(latLngs.get(0), zoom, 0, 0);
options.camera(cameraP);
//TODO MAP IS NULL - SORT OUT!
// check it has been instantiated
if (map != null) {
Log.d(TAG, "map is not null");
map.clear();
//Calculate target zoom, based on trip size
map.animateCamera(CameraUpdateFactory
.newCameraPosition(cameraP));
// Add LatLngs to polyline
PolylineOptions poly = new PolylineOptions().color(Color.RED);
MarkerOptions startMarker = new MarkerOptions()
.position(latLngs.get(0)).title("Start");
MarkerOptions endMarker = null;
if(latLngs.size() > 1) {
endMarker = new MarkerOptions().position(
latLngs.get(latLngs.size() - 1)).title("End");
}
for (LatLng latLng : latLngs) {
poly.add(latLng);
}
map.addPolyline(poly);
map.addMarker(startMarker);
map.addMarker(endMarker);
}
ft = getSupportFragmentManager().beginTransaction();
ft.add(R.id.trip_summary_map_container, mapFragment);
ft.commit();
}
}
As you can see from the inline comments, the map is still null (although it is displaying and using the options). Just can't add things to it. I assume I am not instantiating it properly?
The Activity extends FragmentActivity, and I have set up all the necessary stuff for using the Maps API.
Thank you for any help.
EDIT: I have posted a new answer with the solution I prefer to use now.
I had the same problem some days ago and I solved it extending SupportMapFragment class so that it executes a callback method once the map is finally ready.
public class ExtendedSupportMapFragment extends SupportMapFragment {
public static interface MapReadyListener {
public void mapIsReady(GoogleMap map);
}
#Deprecated
public static SupportMapFragment newInstance() {
return null;
}
#Deprecated
public static SupportMapFragment newInstance(GoogleMapOptions options) {
return null;
}
public static ExtendedSupportMapFragment newInstance(MapReadyListener mapReadyListener) {
ExtendedSupportMapFragment fragment = new ExtendedSupportMapFragment();
fragment.mapReadyListener = mapReadyListener;
return fragment;
}
private MapReadyListener mapReadyListener;
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (mapReadyListener != null)
mapReadyListener.mapIsReady(getMap());
}
}
Then you just need to do something like this:
public class RutaMapaFragment extends SherlockFragment implements ExtendedSupportMapFragment.MapReadyListener {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
fragmentMapa = ExtendedSupportMapFragment.newInstance(RutaMapaFragment.this);
}
...
#Override
public void mapIsReady(GoogleMap map) {
//Do whatever you want with your map.
}
}
So, time has passed and all. The fact is that I don't use the solution in my previous answer anymore, but prefer to use a ViewTreeObserver instead. The following code shows a rather simple fragment with a SupportMapFragment being added to it.
The method createMap() adds the SupportMapFragment and then executes setupMap(), but only via a OnGlobalLayoutListener that will basically be executed once the map is actually ready. Of course, this listener is removed immediately—there's no need to keep it there any longer.
public class MyMapFragment extends Fragment {
private View mMapContainer;
private SupportMapFragment mMapFragment;
private GoogleMap mMap;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(/* ... */);
// ...
mMapContainer = view.findViewById(R.id.map_fragment_container);
createMap();
return view;
}
private void createMap() {
mMapFragment = new SupportMapFragment();
getFragmentManager().beginTransaction()
.replace(R.id.map_fragment_container, mMapFragment)
.commit();
mMapContainer.getViewTreeObserver()
.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#TargetApi(Build.VERSION_CODES.JELLY_BEAN)
#Override
public void onGlobalLayout() {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN) {
mMapContainer.getViewTreeObserver().removeOnGlobalLayoutListener(this);
} else {
mMapContainer.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
mMap = mMapFragment.getMap();
setupMap();
}
});
}
private void setupMap() {
mMap.setMyLocationEnabled(true);
// ...
}
}
Try to add the Marker & PolyLines in the map using below code:
GoogleMap mMap;
static final CameraPosition BONDI =
new CameraPosition.Builder().target(new LatLng(-33.891614, 151.276417))
.zoom(15.5f)
.bearing(300)
.tilt(50)
.build();
changeCamera(CameraUpdateFactory.newCameraPosition(BONDI));
mMap.addMarker(new MarkerOptions().position(new LatLng(-33.891614, 151.276417)).title("Bondi"));
private void changeCamera(CameraUpdate update) {
changeCamera(update, null);
}
/**
* Change the camera position by moving or animating the camera depending on the state of the
* animate toggle button.
*/
private void changeCamera(CameraUpdate update, CancelableCallback callback) {
boolean animated = ((CompoundButton) findViewById(R.id.animate)).isChecked();
if (animated) {
mMap.animateCamera(update, callback);
} else {
mMap.moveCamera(update);
}
}
Add the PolyLines as below:
// A simple polyline with the default options from Melbourne-Adelaide-Perth.
mMap.addPolyline((new PolylineOptions())
.add(BONDI));