I am trying to implement a custom activity that initialises the Fused Location services in Xamarin c# so I can reuse this activity whenever the Fused Location is needed. The problem that I am having is that the map is being loaded before the location services. This way, I cannot animate the camera to zoom in at the user's location since the location is still null.
Here is the custom activity:
using System;
using Android.App;
using Android.Gms.Common;
using Android.Gms.Common.Apis;
using Android.Gms.Maps.Model;
using Android.Locations;
using Android.OS;
using Android.Gms.Location;
using Android.Widget;
namespace Maps.Droid.LocationService {
public class LocationTrackerActivity : Activity, GoogleApiClient.IConnectionCallbacks, GoogleApiClient.IOnConnectionFailedListener, Android.Gms.Location.ILocationListener {
// Static Fields
public static long MIN_DISTANCE_CHANGE_FOR_UPDATES = 5; // 5 meters
public static long MIN_TIME_BW_UPDATES = 1000 * 10; // 10 seconds
private Location currentLocation;
private Activity activity;
private bool hasGooglePlayServices;
private GoogleApiClient mGoogleApiClient;
private LocationRequest mLocationRequest;
private bool locationAvailable = false;
protected override void OnCreate(Bundle savedInstanceState) {
base.OnCreate(savedInstanceState);
this.activity = this;
hasGooglePlayServices = checkPlayServices();
if (hasGooglePlayServices) {
initFusedLocation();
} else {
initAndroidLocation();
}
}
private void initFusedLocation() {
mLocationRequest = new LocationRequest();
mLocationRequest.SetInterval(LocationTrackerActivity.MIN_TIME_BW_UPDATES);
mLocationRequest.SetFastestInterval(LocationTrackerActivity.MIN_TIME_BW_UPDATES / 2);
mLocationRequest.SetSmallestDisplacement(LocationTrackerActivity.MIN_DISTANCE_CHANGE_FOR_UPDATES);
mLocationRequest.SetPriority(LocationRequest.PriorityHighAccuracy);
mGoogleApiClient = new GoogleApiClient.Builder(Application.Context)
.AddConnectionCallbacks(this)
.AddOnConnectionFailedListener(this)
.AddApi(LocationServices.API)
.Build();
}
protected override void OnResume() {
base.OnResume();
if (mGoogleApiClient.IsConnected) {
LocationServices.FusedLocationApi.RequestLocationUpdates(mGoogleApiClient, mLocationRequest, this);
}
}
protected override void OnPause() {
base.OnPause();
// Stop location updates to save battery, but don't disconnect the GoogleApiClient object.
if (mGoogleApiClient.IsConnected) {
LocationServices.FusedLocationApi.RemoveLocationUpdates(mGoogleApiClient, this);
}
}
protected override void OnStart() {
base.OnStart();
mGoogleApiClient.Connect();
}
protected override void OnStop() {
base.OnStop();
// only stop if it's connected, otherwise we crash
if (mGoogleApiClient != null) {
mGoogleApiClient.Disconnect();
}
}
private void initAndroidLocation() {
}
private bool checkPlayServices() {
GoogleApiAvailability apiAvailability = GoogleApiAvailability.Instance;
int resultCode = apiAvailability.IsGooglePlayServicesAvailable(activity);
if (resultCode != ConnectionResult.Success) {
// In case we want to tell the user to install or update Google Play Services
//if (apiAvailability.IsUserResolvableError(resultCode)) {
// apiAvailability.GetErrorDialog(activity, resultCode, PLAY_SERVICES_RESOLUTION_REQUEST).Show();
//} else {
// Toast.MakeText(activity, "This device is not supported", ToastLength.Long).Show();
//}
return false;
}
return true; // has google play services installed
}
public double getLatitude() {
return currentLocation == null ? 0.0 : currentLocation.Latitude;
}
public double getLongitude() {
return currentLocation == null ? 0.0 : currentLocation.Longitude;
}
public bool canGetLocation() {
return locationAvailable;
}
public LatLng getLatLng() {
return new LatLng(currentLocation.Latitude, currentLocation.Longitude);
}
public void OnConnected(Bundle connectionHint) {
// Get last known recent location. If the user launches the activity,
// moves to a new location, and then changes the device orientation, the original location
// is displayed as the activity is re-created.
if (currentLocation == null && mGoogleApiClient.IsConnected) {
currentLocation = LocationServices.FusedLocationApi.GetLastLocation(mGoogleApiClient);
}
Console.WriteLine("location is about to be set to true");
locationAvailable = true;
LocationServices.FusedLocationApi.RequestLocationUpdates(mGoogleApiClient, mLocationRequest, this);
}
public void OnConnectionSuspended(int cause) {
// GoogleApiClient will automatically attempt to restore the connection.
// Applications should disable UI components that require the service, and wait for a call to onConnected(Bundle) to re-enable them
if (cause == GoogleApiClient.ConnectionCallbacks.CauseServiceDisconnected) {
Toast.MakeText(activity, "Location Services disconnected. Please re-connect.", ToastLength.Long).Show();
} else if (cause == GoogleApiClient.ConnectionCallbacks.CauseNetworkLost) {
Toast.MakeText(activity, "Network lost. Please re-connect.", ToastLength.Long).Show();
}
}
public void OnConnectionFailed(ConnectionResult result) {
Console.WriteLine("Connection failed: " + result.ToString());
}
public void OnLocationChanged(Location location) {
currentLocation = location;
}
}
}
Here is the class that inherit the custom class:
using Android.App;
using Android.OS;
using Maps.Droid.LocationService;
namespace Maps.Droid {
[Activity(Label = "Map Activity")]
public class MapActivity : LocationTrackerActivity {
// Properties
protected override void OnCreate(Bundle savedInstanceState) {
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.map_activity);
var mapFrag = new MapViewFragment();
var ft = FragmentManager.BeginTransaction();
ft.Add(Resource.Id.map_container, mapFrag);
ft.Commit();
}
}
}
Here is the fragment in the inherited activity:
using System;
using Android.App;
using Android.OS;
using Android.Views;
using Android.Gms.Maps;
namespace Maps.Droid {
public class MapViewFragment : Fragment, IOnMapReadyCallback {
// private Activity activity;
private GoogleMap map;
private MapActivity parent;
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.Inflate(Resource.Layout.map_fragment, null);
parent = ((MapActivity)Activity);
MapFragment frag = this.FragmentManager.FindFragmentById<MapFragment>(Resource.Id.map);
frag.GetMapAsync(this);
return view;
}
public void OnMapReady(GoogleMap googleMap) {
if (googleMap != null) {
map = googleMap;
var zoomVariance = 0.2;
var defaultZoom = 16f;
var currentZoomLevel = map.CameraPosition.Zoom;
map.MapType = GoogleMap.MapTypeNormal;
map.MyLocationEnabled = true;
map.CameraChange += delegate (object sender, GoogleMap.CameraChangeEventArgs e) {
if (Math.Abs(e.Position.Zoom - currentZoomLevel) < zoomVariance) {
return;
}
currentZoomLevel = e.Position.Zoom;
Console.WriteLine("Zooming " + currentZoomLevel);
};
map.UiSettings.ZoomControlsEnabled = true;
map.UiSettings.CompassEnabled = true;
map.UiSettings.SetAllGesturesEnabled(true); // Zoom, Tilt, Scroll, Rotate
if (parent.canGetLocation()) {
// ***** PROBLEM HERE ******* canGetLocation is set to true just afterwards.
map.AnimateCamera(CameraUpdateFactory.NewLatLngZoom(parent.getLatLng(), defaultZoom)); // Mosaic coordinates
}
}
}
}
}
I was thinking of implementing a call back to LocationTrackerActivity. So when the location services become available the class MapActivity then would be able to load the MapViewFragment within that custom callback. This way the location services would be loaded before the map. Therefore, this part of the code would always execute :
if (parent.canGetLocation()) {
// ***** PROBLEM HERE ******* canGetLocation is set to true just afterwards.
map.AnimateCamera(CameraUpdateFactory.NewLatLngZoom(parent.getLatLng(), defaultZoom)); // Mosaic coordinates
}
However I have no idea how to customise a callback. Maybe there are better solutions to this problem?