Reputation: 11
I am trying to make a xamarin app, with android and ios. I have made a service, to broadcast the users location at interval, but everytime it tries to broadcast (using SendLocationToServer() ) it crashes the app.
[Service]
public class LocationService : Service {
public string role = "";
public string auth = "";
System.Timers.Timer _timer;
const string foregroundChannelId = "location_service_channel";
const int serviceId = 209345;
const string channelId = "location_service_channel";
const string channelName = "Location Service";
const string endpoint = @"REMOVED FOR STACK OVERFLOW POST";
public override IBinder OnBind(Intent intent) {
return null;
}
public override void OnCreate() {
var notificationManager = (NotificationManager)GetSystemService(NotificationService);
if (Build.VERSION.SdkInt >= BuildVersionCodes.O) {
var channel = new NotificationChannel(channelId, channelName, NotificationImportance.Default) {
Description = "Location Service is running"
};
notificationManager.CreateNotificationChannel(channel);
}
}
public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId) {
StartForeground(serviceId, CreateNotification());
auth = intent.GetStringExtra("authToken");
role = intent.GetStringExtra("role");
_timer = new System.Timers.Timer(10000);
_timer.Elapsed += async (sender, e) => {
try {
await SendLocationToServer();
} catch (Exception ex) {
// Log exception
Toast.MakeText(Android.App.Application.Context, "Timer Toast " + ex.Message, ToastLength.Short).Show();
}
};
_timer.AutoReset = true;
_timer.Start();
return StartCommandResult.Sticky;
}
private Notification CreateNotification() {
var intent = new Intent(MainActivity.Instance, typeof(MainActivity));
intent.AddFlags(ActivityFlags.SingleTop);
intent.PutExtra("Title", "Message");
var pendingIntent = PendingIntent.GetActivity(MainActivity.Instance, 0, intent, PendingIntentFlags.UpdateCurrent);
var notificationBuilder = new Notification.Builder(MainActivity.Instance)
.SetContentTitle(channelName)
.SetContentText("Broadcasting location in the background")
.SetSmallIcon(Resource.Drawable.Icon_small)
.SetOngoing(true)
.SetContentIntent(pendingIntent);
if (global::Android.OS.Build.VERSION.SdkInt >= BuildVersionCodes.O) {
NotificationChannel notificationChannel = new NotificationChannel(foregroundChannelId, "Title", NotificationImportance.High);
notificationChannel.Importance = NotificationImportance.High;
notificationChannel.EnableLights(true);
notificationChannel.EnableVibration(true);
notificationChannel.SetShowBadge(true);
notificationChannel.SetVibrationPattern(new long[] { 100, 200, 300 });
var notificationManager = MainActivity.Instance.GetSystemService(Context.NotificationService) as NotificationManager;
if (notificationManager != null) {
notificationBuilder.SetChannelId(foregroundChannelId);
notificationManager.CreateNotificationChannel(notificationChannel);
}
}
return notificationBuilder.Build();
//try {
// var notification = new Notification.Builder(this, channelId)
// .SetContentTitle(channelName)
// .SetContentText("Broadcasting location in the background")
// .SetSmallIcon(Resource.Drawable.Icon_small)
// .SetOngoing(true) // Keep the notification active
// .Build();
// return notification;
//}
//catch(Exception e) {
// Toast.MakeText(Android.App.Application.Context, "Error: " + e.Message, ToastLength.Short).Show();
// return null;
//}
}
private async Task SendLocationToServer() {
if (!string.IsNullOrEmpty(role) && !string.IsNullOrEmpty(auth)) {
using (var client = new HttpClient()) {
try {
var location = await Geolocation.GetLocationAsync(new GeolocationRequest(GeolocationAccuracy.Best));
if (location != null) {
var gpsData = new {
AuthToken = auth,
Role = role,
Longitude = location.Longitude,
Latitude = location.Latitude
};
var jsonContent = JsonConvert.SerializeObject(gpsData);
var content = new StringContent(jsonContent, Encoding.UTF8, "application/json");
var response = await client.PostAsync(endpoint + "/logbeacon", content);
//i++;
if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized) {
Toast.MakeText(Android.App.Application.Context, "Unauthorized. Please login again", ToastLength.Short).Show();
} else if (response.StatusCode == System.Net.HttpStatusCode.OK) {
Toast.MakeText(Android.App.Application.Context, "Check broadcast 3", ToastLength.Short).Show();
} else {
Toast.MakeText(Android.App.Application.Context, "Connection Error. ", ToastLength.Short).Show();
}
} else {
}
} catch (Exception ex) {
// Handle exception
Toast.MakeText(Android.App.Application.Context, "Check broadcast 4 " + ex.Message, ToastLength.Short).Show();
await client.GetAsync(endpoint + "/test/" + ex.Message); // TESTING
}
}
}
}
public override void OnDestroy() {
_timer?.Stop();
_timer?.Dispose();
base.OnDestroy();
}
}
And the Implementer:
[assembly: Xamarin.Forms.Dependency(typeof(LocationServiceImplementation))]
namespace MyAppName.Droid
{
public class LocationServiceImplementation : ILocationService {
public void StartLocationService(string role, string auth) {
Toast.MakeText(Android.App.Application.Context, "Starting", ToastLength.Short).Show();
var intent = new Intent(MainActivity.Instance, typeof(LocationService));
intent.PutExtra("auth", auth);
intent.PutExtra("role", role);
if (Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.O) {
MainActivity.Instance.StartForegroundService(intent);
} else {
MainActivity.Instance.StartService(intent);
}
}
public void StopLocationService() {
Toast.MakeText(Android.App.Application.Context, "Stopping", ToastLength.Short).Show();
throw new NotImplementedException();
}
}
The permissions are set and requested, in Manifest and MainActivity: This include coarse location, fine location, and always location. The order of the request took some time to figure out, but are requested correcly now.
This function is in the MainActivity of the android project:
public async Task GetLocationConsent() {
var status = await Permissions.CheckStatusAsync<Permissions.LocationWhenInUse>();
if (status == PermissionStatus.Denied || status == PermissionStatus.Unknown) {
status = await Permissions.RequestAsync<Permissions.LocationWhenInUse>();
}
// Only request background location on Android 10 (API level 29) or higher
if (status == PermissionStatus.Granted && Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.Q) {
var backgroundStatus = await Permissions.CheckStatusAsync<Permissions.LocationAlways>();
if (backgroundStatus == PermissionStatus.Denied || backgroundStatus == PermissionStatus.Unknown) {
await Permissions.RequestAsync<Permissions.LocationAlways>();
// Check again if permission is not granted, then navigate to settings
backgroundStatus = await Permissions.CheckStatusAsync<Permissions.LocationAlways>();
if (backgroundStatus != PermissionStatus.Granted) {
// Inform the user to manually enable background location in settings
Toast.MakeText(Application.Context, "Please enable Location Always in App Permissions.", ToastLength.Long).Show();
var intent = new Intent(Android.Provider.Settings.ActionApplicationDetailsSettings);
intent.AddFlags(ActivityFlags.NewTask);
var uri = Android.Net.Uri.FromParts("package", Application.Context.PackageName, null);
intent.SetData(uri);
Application.Context.StartActivity(intent);
// Show a message to guide the user
Toast.MakeText(Application.Context, "Please enable Background Location in App Permissions.", ToastLength.Long).Show();
}
}
}
}
I have been trying to crack this nut for so many hours that I can't see straight, and work the deadline is rapidly approaching... Anyhelp would be grately appreciated. Please.
I am trying to run a background service on android, that periodically broadcasts the users location, but the app crashes when I try.
Upvotes: 0
Views: 29
Reputation: 11
Apparently the problem was the "async". Once I changed the var location = await Geolocation.GetLocationAsync(new GeolocationRequest(GeolocationAccuracy.Best));
Into this
var location = await Task.Run(async () => await Geolocation.GetLocationAsync(new GeolocationRequest(GeolocationAccuracy.Best)));
And changed
SendLocationToServer()
to no longer be async, the code ran smothly. I don't know why the code didn't like running async, but isolating that async part into a singular Task, solved the crash.
Upvotes: 1