0

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.

5
  • Where is the stacktrace from the crash? Commented Oct 14, 2024 at 14:37
  • My android studio, for some reason, cant create an emulation that I can test on. So I have been building an APK and running it on my android smartphone. I have been using Toast.MakeText() for debugging, beacuse I couldn't get the stack trace. I know it would be so much easier to have the stack trace, but this will have to make due... Commented Oct 14, 2024 at 15:56
  • As a step in the right direction, I have enabled the commented out section for creating the notification, instead of the current notification creationg. For clarification, this is the part I use for notification now: var notification = new Notification.Builder(this, foregroundChannelId) .SetContentTitle(channelName) .SetContentText("Broadcasting location in the background") .SetSmallIcon(Resource.Drawable.Icon_small) .SetOngoing(true) // Keep the notification active .Build(); return notification; Commented Oct 14, 2024 at 16:01
  • I don't understand why running on a physical device is stopping you from getting a stacktrace. Just grab it from the logcat output. Commented Oct 14, 2024 at 16:19
  • I solved it! 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. Thanks for the help, gonna read up on logcat for future bugs. Commented Oct 15, 2024 at 4:35

1 Answer 1

1

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.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.