I'm developing an app that has a app feature for displaying teams on Google maps as markers.
I'm able to display myself as marker that updates when I move & others as markers on a fragment.
The issue is markers are displayed ONLY THE 1st TIME that I go to MapFragment. When I navigate away to another fragment and come back to the map, I see an EMPTY map with no markers & no zoom buttons.
Attempt #3, please see history for previous implementations which are slightly different:
My fragment layout:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/incident_map"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="com.google.android.gms.maps.SupportMapFragment"
/>
<ProgressBar
android:id="@+id/incident_map_progress_bar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="visible"/>
<!--<com.google.android.gms.maps.MapView
android:id="@+id/incident_map"
android:layout_width="match_parent"
android:layout_height="match_parent" />-->
</RelativeLayout>
My IncidentMapFragment code, which is now updated based on user feedback. The updates are minimal. Instead on onResume() im using onActivityCreated() and instead of onPause() Im using onSaveInstanceState(), see updated code below
package com.xyz.fragments;
//i did not include imports
//Based on https://developers.google.com/maps/documentation/android-sdk/map-with-marker
public class IncidentMapFragment extends Fragment implements OnMapReadyCallback {
public static final String TAG = "IncidentMapFragment";
private static IncidentMapFragment incidentMapFragment = null;
public static IncidentMapFragment instance() {
if (incidentMapFragment==null)
{
incidentMapFragment = new IncidentMapFragment();
}
return incidentMapFragment;
}
private MapView mapView;
private static GoogleMap map;
private ProgressBar progressBar;
private SupportMapFragment mapFragment;
public static final int UPDATE_MY_CURRENT_LOCATION = 0;
public static final int UPDATE_MY_TEAMS_CURRENT_LOCATIONS = 1;
public static final int UPDATE_ALL_TEAMS_CURRENT_LOCATIONS = 2;
public static final int UPDATE_ALL_LOCATION_BASED_EVENTS = 3;
private static Context context;
private int MY_DEFINED_ACCESS_COARSE_LOCATION_CALLBACK_RESULT = 1;
private int MY_DEFINED_ACCESS_FINE_LOCATION_CALLBACK_RESULT = 2;
boolean flagCoarseLocation = false;
boolean flagFineLocation = false;
private WamsUnitVehicleUnitRelationshipDao vehicleUnitRelationshipDao = new WamsUnitVehicleUnitRelationshipDaoImpl();
private static WamsUnitVehicleUnitRelationship newVehicleUnitRelationship = null;
private static CameraPosition cp;
private static Bundle markersBundle = new Bundle();
private static Bundle saveStateBundle = new Bundle();
private boolean createdStateInDestroyView = false;
private static final String SAVED_BUNDLE_TAG = "IncidentMapFragment_SAVE_STATE";
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.incident_map_fragment, container, false);
context = rootView.getContext();
progressBar = rootView.findViewById(R.id.incident_map_progress_bar);
try {
FragmentManager fm = getActivity().getSupportFragmentManager();
if (savedInstanceState == null) {
mapFragment = SupportMapFragment.newInstance();
fm.beginTransaction().replace(R.id.incident_map, mapFragment,TAG).commit();
}
else {
mapFragment = (SupportMapFragment) fm
.findFragmentByTag(TAG);
}
if (savedInstanceState!=null) {
saveStateBundle = savedInstanceState.getBundle(SAVED_BUNDLE_TAG);
//restore camera
cp = saveStateBundle.getParcelable("cp");
//restore bundle of markers
markersBundle = saveStateBundle.getParcelable("markersBundle");
if (cp!=null && markersBundle!=null)
{
reDrawMarkers(markersBundle);
}
}
} catch (Exception exc) {
Log.e(TAG, exc.getMessage());
exc.printStackTrace();
}
return rootView;
}
@Override
public void onActivityCreated(Bundle bundle) {
super.onActivityCreated(bundle);
Log.i(TAG,"onActivityCreated()");
newVehicleUnitRelationship = vehicleUnitRelationshipDao.getWamsUnitVehicleUnitRelationship(NgfrApp.getInstance().getUniqueDeviceId());
if (mapFragment!=null) {
mapFragment.getMapAsync(this);
}
}
@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
Log.i(TAG,"onSaveInstanceState()");
outState.putBundle(SAVED_BUNDLE_TAG, saveState());
createdStateInDestroyView = false;
super.onSaveInstanceState(outState);
}
@Override
public void onDestroyView() {
super.onDestroyView();
saveStateBundle = saveState();
createdStateInDestroyView = true;
cp = null;
markersBundle = null;
}
private Bundle saveState() {
Bundle state = new Bundle();
state.putParcelable("cp", cp);
state.putParcelable("markersBundle", markersBundle);
return state;
}
/*Handler used by outside class such as MqttBroker.
1) UPDATE_MY_CURRENT_LOCATION. When my location service send a update location(lat,long) call updateMarkersOnMap() which creates a new AsyncTask to update teh display
2) UPDATE_MY_TEAMS_CURRENT_LOCATIONS. When the MQTT dservice gets new location(lat,long) call updateMarkersOnMap() which creates a new AsyncTask to update teh display
3) UPDATE_ALL_TEAMS_CURRENT_LOCATIONS, not implemented.
*/
public static final Handler updateIncidentMap = new Handler(Looper.getMainLooper()) {
public void handleMessage(Message msg) {
//if the context is null then the MapFragment & GoogleMap objects are NOT instantiated and updating the maps non-existant UI will cause exceptions, NPE, and crashes!
if (context != null) {
Location myCurrentLocation = null;
final int what = msg.what;
switch (what) {
case UPDATE_MY_CURRENT_LOCATION:
Log.i(TAG,"UPDATE_MY_CURRENT_LOCATION");
myCurrentLocation = (Location) msg.obj;
if (map != null) {
updateMarkersOnMap(map,markersBundle, myCurrentLocation.getLatitude(),myCurrentLocation.getLongitude(),newVehicleUnitRelationship.getWamsId(), newVehicleUnitRelationship.getVehicleUnitId());
}
break;
case UPDATE_MY_TEAMS_CURRENT_LOCATIONS:
Log.i(TAG,"UPDATE_MY_TEAMS_CURRENT_LOCATIONS");
if (map != null) {
WamsLocationMarker wamsLocationMarker = (WamsLocationMarker) msg.obj;
updateMarkersOnMap(map, markersBundle,wamsLocationMarker.getLat(),wamsLocationMarker.getLon(), wamsLocationMarker.getClientId(), wamsLocationMarker.getVehicleId());
//mock team
mockTeam(map,markersBundle, wamsLocationMarker.getLat(),wamsLocationMarker.getLon());
}
break;
case UPDATE_ALL_TEAMS_CURRENT_LOCATIONS:
break;
default:
break;
}
} //end if context is NOt null
} //end handleMessage
};
/*I added the @SuppressLint("MissingPermission") because we are handling this in permissionHelper(getActivity()),
and the IDE is too naive to know*/
@SuppressLint("MissingPermission")
@Override
public void onMapReady(GoogleMap googleMap) {
Log.i(TAG, "onMapReady()");
try {
//remove progress bar
progressBar.setVisibility(GONE);
//initially zoom in my location
map = googleMap;
if (permissionHelper(getActivity()))
{
map.setMyLocationEnabled(true);
map.getUiSettings().setZoomControlsEnabled(true);
map.getUiSettings().setCompassEnabled(true);
}
if (cp != null) {
map.moveCamera(CameraUpdateFactory.newCameraPosition(cp));
cp = null;
}
reDrawMarkers(markersBundle);
}
catch (NullPointerException exc)
{
exc.printStackTrace();
}
}
private static void updateMarkersOnMap(GoogleMap map,Bundle bundle, double lat,double log, String id, String vehicleId) {
final GoogleMap _map = map;
final double _lat = lat;
final double _log = log;
final String _id = id;
final String _vehicleId = vehicleId;
new AsyncTask < Void, Void, WamsLocationMarker > () {
boolean update = false;
@Override
protected WamsLocationMarker doInBackground(Void...voids) {
Marker marker = null;
WamsLocationMarker wamsLocationMarker=null;
try {
Log.i(TAG,"async map: "+map);
if (_map != null && bundle!=null)
{
Log.i(TAG,_map.toString());
//if the wamsLocationMarker exists, then UPDATE
if (bundle.containsKey(_id)) {
Log.i(TAG,"markersBundle.containsKey(_id): "+ bundle.containsKey(_id));
Log.i(TAG,"update true");
//get from hashmap
wamsLocationMarker = (WamsLocationMarker)bundle.get(_id);
update = true;
} else {
//add to map
//if the ids are equal then this is YOU
wamsLocationMarker = new WamsLocationMarkerFactory().createWamsLocationMarker(_id, _vehicleId, _lat, _log);
Log.i(TAG,"WamsLocationMarker");
Log.i(TAG,"update false");
}
} else {
Log.e(TAG, " updateMarkersOnMap() map is " + _map);
}
}
catch (NullPointerException exc)
{
exc.printStackTrace();
}
return wamsLocationMarker;
}
@Override
protected void onPostExecute(WamsLocationMarker wamsLocationMarker) {
super.onPostExecute(wamsLocationMarker);
try {
if (wamsLocationMarker != null) {
Log.i(TAG,"onPostExecute() update:"+update+",wamsLocationMarker:"+wamsLocationMarker);
if (update) {
Log.i(TAG,"onPostExecute() update:"+update);
//UPDATE wth new lat & long if the markers coordinates have change, else dont redraw, beacuse the draw causes a refresh affect & its also a waste computationally
if (wamsLocationMarker.getMarker().getPosition().latitude != _lat && wamsLocationMarker.getMarker().getPosition().longitude != _log) {
LatLng latLng = new LatLng(_lat, _log);
//UPDATE THE MARKER POSITION
wamsLocationMarker.getMarker().setPosition(latLng);
}
} else {
//ADD A NEW MARKER
Marker marker = _map.addMarker(wamsLocationMarker.getMakerOptions());
Log.i(TAG,"ASYNC MARKER:"+marker.getId());
wamsLocationMarker.setMarker(marker);
markersBundle.remove(wamsLocationMarker.getClientId());
markersBundle.putParcelable(wamsLocationMarker.getClientId(), wamsLocationMarker);
}
}
}
catch (NullPointerException exc)
{
exc.printStackTrace();
}
}
}.execute();
}
public void reDrawMarkers(Bundle bundle) {
Log.i(TAG,"reDrawMarkers()");
if (bundle!=null) {
Set<String> keys = bundle.keySet();
WamsLocationMarker wamsLocationMarker = null;
for (String k : keys) {
Log.i(TAG, "key:" + k);
wamsLocationMarker = (WamsLocationMarker) bundle.getParcelable(k);
updateMarkersOnMap(map, bundle, wamsLocationMarker.getLat(), wamsLocationMarker.getLon(), wamsLocationMarker.getClientId(), wamsLocationMarker.getVehicleId());
}
}
}
//Just for test - Im mocking some there points to represent other teams mates on the map, this is just for testing
private static void mockTeam(GoogleMap map, Bundle bundle, double _lat, double _log) {
updateMarkersOnMap(map,bundle, _lat+0.001, _log , "mock111111111", newVehicleUnitRelationship.getVehicleUnitId());
updateMarkersOnMap(map,bundle,_lat, _log+0.001 ,"mock222222222", newVehicleUnitRelationship.getVehicleUnitId());
updateMarkersOnMap(map,bundle, _lat-0.001, _log,"mock33333333", newVehicleUnitRelationship.getVehicleUnitId());
updateMarkersOnMap(map,bundle, _lat, _log-0.001,"mock444444444", newVehicleUnitRelationship.getVehicleUnitId());
}
//Ask the user if required & attempt to grant required location services
private boolean permissionHelper(Activity activity) {
String permission = Manifest.permission.ACCESS_COARSE_LOCATION;
int res = getContext().checkCallingPermission(permission);
//if the required coarse & fine permissions are granted then return true else proceed to get the permissions
if (res == PackageManager.PERMISSION_GRANTED) {
Log.i(TAG, "ACCESS_COARSE_LOCATION GRANTED");
permission = Manifest.permission.ACCESS_FINE_LOCATION;
res = getContext().checkCallingPermission(permission);
if (res == PackageManager.PERMISSION_GRANTED) {
Log.i(TAG, "ACCESS_FINE_LOCATION GRANTED");
flagFineLocation = true;
} else {
Log.i(TAG, "ACCESS_FINE_LOCATION NOT GRANTED");
}
flagCoarseLocation = true;
}
//prompt user for COARSE location permission. If the user cancel then display toast & navigate back
if (!flagCoarseLocation) {
if (ActivityCompat.shouldShowRequestPermissionRationale(activity,
Manifest.permission.ACCESS_COARSE_LOCATION)) {
dialogForCoarsePermission();
} else {
ActivityCompat.requestPermissions(activity,
new String[] {
Manifest.permission.ACCESS_COARSE_LOCATION
},
MY_DEFINED_ACCESS_COARSE_LOCATION_CALLBACK_RESULT);
flagCoarseLocation = true;
}
}
//prompt user for FINE location permission. If the user cancel then display toast & navigate back
if (!flagFineLocation) {
if (ActivityCompat.shouldShowRequestPermissionRationale(activity,
Manifest.permission.ACCESS_FINE_LOCATION)) {
dialogForFinePermission();
} else {
ActivityCompat.requestPermissions(activity,
new String[] {
Manifest.permission.ACCESS_COARSE_LOCATION
},
MY_DEFINED_ACCESS_FINE_LOCATION_CALLBACK_RESULT);
flagFineLocation = true;
}
}
if (!flagCoarseLocation)
{
Log.i(TAG, "ACCESS_COARSE_LOCATION NOT GRANTED");
}
else
{
Log.i(TAG, "ACCESS_COARSE_LOCATION GRANTED");
}
if (!flagFineLocation)
{
Log.i(TAG, "ACCESS_FINE_LOCATION NOT GRANTED");
}
else
{
Log.i(TAG, "ACCESS_FINE_LOCATION GRANTED");
}
return (flagCoarseLocation && flagFineLocation);
}
private void dialogForCoarsePermission() {
DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case DialogInterface.BUTTON_POSITIVE:
//Yes button clicked
ActivityCompat.requestPermissions(getActivity(),
new String[] {
Manifest.permission.ACCESS_COARSE_LOCATION
},
MY_DEFINED_ACCESS_COARSE_LOCATION_CALLBACK_RESULT);
break;
case DialogInterface.BUTTON_NEGATIVE:
//No button clicked
break;
}
}
};
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setMessage("The map requires coarse location permission.Please it by pressing Yes. ").setPositiveButton("Yes", dialogClickListener)
.setNegativeButton("No", dialogClickListener).show();
}
private void dialogForFinePermission() {
DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case DialogInterface.BUTTON_POSITIVE:
//Yes button clicked
ActivityCompat.requestPermissions(getActivity(),
new String[] {
Manifest.permission.ACCESS_FINE_LOCATION
},
MY_DEFINED_ACCESS_FINE_LOCATION_CALLBACK_RESULT);
break;
case DialogInterface.BUTTON_NEGATIVE:
//No button clicked
break;
}
}
};
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setMessage("The map ALSO requires fine location permission.Please it by pressing Yes. ").setPositiveButton("Yes", dialogClickListener)
.setNegativeButton("No", dialogClickListener).show();
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
Log.i(TAG, "onRequestPermissionsResult() request code:" + requestCode); // Log printed
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == MY_DEFINED_ACCESS_COARSE_LOCATION_CALLBACK_RESULT) {
flagCoarseLocation = true;
Toast.makeText(context, "Coarse location permission granted.", Toast.LENGTH_SHORT).show();
}
if (requestCode == MY_DEFINED_ACCESS_FINE_LOCATION_CALLBACK_RESULT) {
flagFineLocation = true;
Toast.makeText(context, "Fine location permission granted.", Toast.LENGTH_SHORT).show();
}
}
}
I provided the GUI & corresponding logs for this fragment only.
The 1st time I navigate to the fragment. Everything is ok, and you see the go to my location button & zoom buttons:
Now the issue appears when I navigate to 1st fragment called "Services" or if leave app in the background for a while. I cant see the markers anymore & new location updates coming via the network are added however are NOT being drawn. Map zoom buttons are also missing!!!
08-16 08:06:03.358 1314-1314/com.xyz I/IncidentMapFragment: onSaveInstanceState()
08-16 08:06:03.836 1314-1314/com.xyz I/IncidentMapFragment: reDrawMarkers()
key:015140000100161
key:mock33333333
key:mock444444444
key:mock111111111
key:mock222222222
08-16 08:06:03.836 1314-1340/com.xyz I/IncidentMapFragment: async map: com.google.android.gms.maps.GoogleMap@1e72e3b
com.google.android.gms.maps.GoogleMap@1e72e3b
markersBundle.containsKey(_id): true
update true
async map: com.google.android.gms.maps.GoogleMap@1e72e3b
com.google.android.gms.maps.GoogleMap@1e72e3b
markersBundle.containsKey(_id): true
update true
async map: com.google.android.gms.maps.GoogleMap@1e72e3b
com.google.android.gms.maps.GoogleMap@1e72e3b
markersBundle.containsKey(_id): true
update true
async map: com.google.android.gms.maps.GoogleMap@1e72e3b
com.google.android.gms.maps.GoogleMap@1e72e3b
markersBundle.containsKey(_id): true
update true
async map: com.google.android.gms.maps.GoogleMap@1e72e3b
com.google.android.gms.maps.GoogleMap@1e72e3b
markersBundle.containsKey(_id): true
update true
08-16 08:06:03.837 1314-1314/com.xyz I/IncidentMapFragment: onActivityCreated()
08-16 08:06:03.851 1314-1314/com.xyz I/IncidentMapFragment: onPostExecute() update:true,wamsLocationMarker:com.xyz.mapping.WamsLocationMarker@19b04df
onPostExecute() update:true
onPostExecute() update:true,wamsLocationMarker:com.xyz.mapping.WamsLocationMarker@47c13e2
onPostExecute() update:true
onPostExecute() update:true,wamsLocationMarker:com.xyz.mapping.WamsLocationMarker@6b22ea9
onPostExecute() update:true
onPostExecute() update:true,wamsLocationMarker:com.xyz.mapping.WamsLocationMarker@e387818
08-16 08:06:03.852 1314-1314/com.xyz I/IncidentMapFragment: onPostExecute() update:true
onPostExecute() update:true,wamsLocationMarker:com.xyz.mapping.WamsLocationMarker@dab0d7
onPostExecute() update:true
onMapReady()
ACCESS_COARSE_LOCATION GRANTED
ACCESS_FINE_LOCATION GRANTED
08-16 08:06:03.853 1314-1314/com.xyz I/IncidentMapFragment: reDrawMarkers()
08-16 08:06:03.854 1314-1314/com.xyz I/IncidentMapFragment: key:015140000100161
key:mock33333333
key:mock444444444
key:mock111111111
key:mock222222222
08-16 08:06:03.854 1314-1338/com.xyz I/IncidentMapFragment: async map: com.google.android.gms.maps.GoogleMap@b7d1933
com.google.android.gms.maps.GoogleMap@b7d1933
markersBundle.containsKey(_id): true
update true
async map: com.google.android.gms.maps.GoogleMap@b7d1933
com.google.android.gms.maps.GoogleMap@b7d1933
markersBundle.containsKey(_id): true
update true
async map: com.google.android.gms.maps.GoogleMap@b7d1933
com.google.android.gms.maps.GoogleMap@b7d1933
markersBundle.containsKey(_id): true
update true
async map: com.google.android.gms.maps.GoogleMap@b7d1933
com.google.android.gms.maps.GoogleMap@b7d1933
markersBundle.containsKey(_id): true
update true
async map: com.google.android.gms.maps.GoogleMap@b7d1933
com.google.android.gms.maps.GoogleMap@b7d1933
markersBundle.containsKey(_id): true
update true
08-16 08:06:03.865 1314-1314/com.xyz I/IncidentMapFragment: onPostExecute() update:true,wamsLocationMarker:com.xyz.mapping.WamsLocationMarker@19b04df
onPostExecute() update:true
onPostExecute() update:true,wamsLocationMarker:com.xyz.mapping.WamsLocationMarker@47c13e2
onPostExecute() update:true
onPostExecute() update:true,wamsLocationMarker:com.xyz.mapping.WamsLocationMarker@6b22ea9
onPostExecute() update:true
onPostExecute() update:true,wamsLocationMarker:com.xyz.mapping.WamsLocationMarker@e387818
onPostExecute() update:true
onPostExecute() update:true,wamsLocationMarker:com.xyz.mapping.WamsLocationMarker@dab0d7
onPostExecute() update:true
See debug window: state is restored but not drawing
My logs show the markers are being added again with no errors or exceptions, but not being displayed?** Why? And how can i fix this?
Thanks