You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I have a location tracking implementation that's showing different behaviors on Android and iOS:
On Android:
Works as expected with minimum timer intervals
Properly tracks location changes
Functions in both background and terminated states
On iOS:
Only works during the first app launch
Functions when app is minimized (background)
Stops tracking when app is terminated
I attempted to solve this by implementing CLLocation for iOS background tracking, which worked for getting location updates, but I couldn't integrate it with my existing OnGpsReading event handler where the core logic resides.
Through research, I found discussions suggesting using only Shiny.NET location services (rather than platform-specific implementations) for handling all location tracking scenarios, including background and terminated states works as expected.
Also the location permission is set to always and background refresh is also added.
Thank you for taking the time to review my issue. I look forward to working together to find a solution?
Expected Behavior
On iOS:
Works all the time.
Should publish to api when iOS is in the background.
Actual Behavior
On iOS:
Only works during the first app launch
Functions when app is minimized (background)
Stops tracking when app is terminated
Exception or Log output
No response
Code Sample
Info.Plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>com.response24.plus.refresh</string>
</array>
<key>CFBundleIdentifier</key>
<string>com.response24.plus</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIDeviceFamily</key>
<array>
<integer>1</integer>
<integer>2</integer>
</array>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>arm64</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>XSAppIconAssets</key>
<string>Assets.xcassets/appicon.appiconset</string>
<!-- Location permissions -->
<key>NSLocationWhenInUseUsageDescription</key>
<string>We need access to location to provide accurate location-based services</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>We need background location access to provide continuous location-based services</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>We need background location access to provide continuous location-based services</string>
<!-- Camera permissions -->
<key>NSCameraUsageDescription</key>
<string>We need camera access to capture photos</string>
<!-- Contacts permissions -->
<key>NSContactsUsageDescription</key>
<string>We need access to contacts to provide enhanced contact-based features</string>
<!-- Notifications -->
<key>NSNotificationUsageDescription</key>
<string>We need to send you notifications for important updates and alerts</string>
<!-- Notification Permissions (if not already present) -->
<key>NSUserNotificationUsageDescription</key>
<string>We need to send you notifications about location updates</string>
<!-- Network capabilities -->
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>location</string>
<string>remote-notification</string>
</array>
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>com.response24.plus.refresh</string>
</array>
</dict>
</plist>
ShinyGpsDelegate
using Microsoft.Extensions.Logging;
using R24_Maui_App.Core.Services.MQTT;
using R24_Maui_App.Core.Services.SQLite;
using R24_Maui_App.Core.Utils.DeviceInfo;
using Shiny;
using Shiny.Locations;
using System.Globalization;
using System.Text.Json;
namespace R24_Maui_App.Core.Delegate
{
public partial class ShinyGpsDelegate : GpsDelegate, Shiny.Locations.IGpsDelegate
{
private readonly ILogger<ShinyGpsDelegate> _logger;
private readonly IMqttService _mqttService;
private bool _isReconnecting = false;
private const int NOTIFICATION_ID = 1001;
private readonly ISQLiteService _sqliteService;
public ShinyGpsDelegate(
ILogger<ShinyGpsDelegate> logger,
IMqttService mqttService,
ISQLiteService sqliteService) : base(logger)
{
_logger = logger;
_mqttService = mqttService;
_sqliteService = sqliteService;
this.MinimumDistance = Distance.FromMeters(10);
this.MinimumTime = TimeSpan.FromSeconds(10);
}
// Add a public method to handle external readings
public async Task HandleLocationUpdate(GpsReading reading)
{
await OnGpsReading(reading);
}
protected override async Task OnGpsReading(GpsReading reading)
{
try
{
_logger.LogInformation($"OnGpsReading : Started");
// Create and save new location entry to SQLite
var locationEntry = new LocationEntry
{
Latitude = reading.Position.Latitude,
Longitude = reading.Position.Longitude,
Timestamp = reading.Timestamp.DateTime,
Accuracy = reading.PositionAccuracy,
Speed = reading.Speed,
Heading = reading.Heading,
IsPublished = false
};
await _sqliteService.SaveLocationAsync(locationEntry);
_logger.LogInformation($"OnGpsReading : GPS reading saved to SQLite - Lat: {reading.Position.Latitude}, Lon: {reading.Position.Longitude}");
// Handle MQTT connection and publishing
if (!_mqttService.IsConnected && !_isReconnecting)
{
_isReconnecting = true;
try
{
await _mqttService.Connect();
await _mqttService.SubscribeToTopic();
_logger.LogInformation("OnGpsReading : Successfully reconnected to MQTT broker");
}
catch (Exception ex)
{
_logger.LogError(ex, "OnGpsReading : Failed to reconnect to MQTT broker");
}
finally
{
_isReconnecting = false;
}
}
// If connected to MQTT, process unpublished entries
if (_mqttService.IsConnected)
{
try
{
var unpublishedLocations = await _sqliteService.GetUnpublishedLocationsAsync();
_logger.LogInformation($"OnGpsReading : Found {unpublishedLocations.Count} unpublished locations");
foreach (var location in unpublishedLocations)
{
try
{
await _mqttService.PublishAsync(
location.Latitude.ToString("F7", CultureInfo.InvariantCulture),
location.Longitude.ToString("F7", CultureInfo.InvariantCulture),
location.Timestamp
);
// Mark as published and update SQLite
location.IsPublished = true;
await _sqliteService.UpdateLocationAsync(location);
_logger.LogInformation($"OnGpsReading : Successfully published and updated location ID: {location.Id}");
}
catch (Exception mqttEx)
{
_logger.LogError(mqttEx, $"OnGpsReading : Failed to publish location ID: {location.Id}. Will retry on next reading.");
break; // Break the loop on first failure to try again next time
}
}
// Clean up published entries
await _sqliteService.DeletePublishedLocationsAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, "OnGpsReading : Error processing unpublished locations");
}
}
#if ANDROID
// Update notification if needed
var activity = ActivityStateManager.Default.GetCurrentActivity();
if (Application.Current?.MainPage != null && activity != null)
{
MainThread.BeginInvokeOnMainThread(() =>
{
UpdateNotification(reading);
});
}
#endif
}
catch (Exception ex)
{
_logger.LogError(ex, "OnGpsReading : Failed to process GPS reading");
}
}
#if ANDROID
public void Configure(AndroidX.Core.App.NotificationCompat.Builder builder)
{
builder
.SetOngoing(true) // Notification cannot be dismissed by user
.SetAutoCancel(false) // Notification persists after tap
.SetContentTitle("Location Tracking Active")
.SetContentText("Tracking your location in background")
.SetSmallIcon(Android.Resource.Drawable.IcMenuMyLocation)
.SetPriority((int)Android.App.NotificationPriority.Default)
.SetCategory(AndroidX.Core.App.NotificationCompat.CategoryService)
.SetVisibility(AndroidX.Core.App.NotificationCompat.VisibilityPrivate)
.SetForegroundServiceBehavior(AndroidX.Core.App.NotificationCompat.ForegroundServiceImmediate)
.SetChannelId("location_service") // Must match channel created in your MainActivity
.SetOnlyAlertOnce(true); // Notify only on first creation
}
private void UpdateNotification(GpsReading reading)
{
try
{
var context = Platform.CurrentActivity ?? Android.App.Application.Context;
var notificationManager = context.GetSystemService(Android.Content.Context.NotificationService) as Android.App.NotificationManager;
var builder = new AndroidX.Core.App.NotificationCompat.Builder(context, "location_service")
.SetOngoing(true)
.SetAutoCancel(false)
.SetContentTitle("Location Tracking Active")
.SetContentText($"Lat: {reading.Position.Latitude:F4}, Lon: {reading.Position.Longitude:F4}")
.SetSmallIcon(Android.Resource.Drawable.IcMenuMyLocation)
.SetPriority((int)Android.App.NotificationPriority.Default);
notificationManager?.Notify(NOTIFICATION_ID, builder.Build());
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to update notification");
}
}
private string FormatLocationMessage(GpsReading reading)
{
return $"Latitude: {reading.Position.Latitude:F7}\n" +
$"Longitude: {reading.Position.Longitude:F7}\n" +
$"Heading: {reading.Heading:F1}°\n" +
$"Accuracy: {reading.PositionAccuracy:F1}m\n" +
$"Speed: {reading.Speed:F1} m/s\n" +
$"Timestamp: {reading.Timestamp:yyyy-MM-dd HH:mm:ss}";
}
#endif
}
}
How are you starting it? What's the error/console messaging? You have to submit some sort of reproducible sample to show this! I'm working with an app right now that starts/stops GPS repeatedly and across app sessions without issue.
Should publish to api when iOS is in the background
This doesn't have anything to do with Shiny or the bg service unfortunately. It isn't like we do anything to block it. If your GPS pings are coming in slowly, the cancellation token could be called if the call is taking too long. Again... submit an actual repro so I can understand the issue.
Stops tracking when app is terminated
This is how iOS works. Shiny has to operate within the rules of the platform. Shiny will try to restart GPS automatically when the app is restarted.
I just want to confirm — when the app is terminated, it won’t be able to trigger a location change even if the device is moved within a short period, like 10 minutes. The reason I’m asking is that this is a security app, and we need to receive constant location updates. On Android, background tracking works perfectly.
I just want to confirm — when the app is terminated, it won’t be able to trigger a location change even if the device is moved within a short period, like 10 minutes. The reason I’m asking is that this is a security app, and we need to receive constant location updates. On Android, background tracking works perfectly.
If you want a more thorough explanation than my answer, please read the iOS documentation on corelocation. I'm just offering code to help with cross platform.
Also added you to the repo ;), thank you
Please make repos publicly available on github. Also, this isn't a repro that you've sent to me, this is looking more like a full app. I'm happy to fix a bug within the library if you can clearly show it in a small repro.
Component/Nuget
GPS or Geofencing (Shiny.Locations)
What operating system(s) are effected?
Version(s) of Operation Systems
Physical Device : 15.8.3
Mac : Sequoia 15.2
Hosting Model
Steps To Reproduce
I have a location tracking implementation that's showing different behaviors on Android and iOS:
On Android:
On iOS:
I attempted to solve this by implementing CLLocation for iOS background tracking, which worked for getting location updates, but I couldn't integrate it with my existing OnGpsReading event handler where the core logic resides.
Through research, I found discussions suggesting using only Shiny.NET location services (rather than platform-specific implementations) for handling all location tracking scenarios, including background and terminated states works as expected.
Also the location permission is set to always and background refresh is also added.
Thank you for taking the time to review my issue. I look forward to working together to find a solution?
Expected Behavior
On iOS:
Actual Behavior
On iOS:
Exception or Log output
No response
Code Sample
Info.Plist
ShinyGpsDelegate
MauiProgram.cs
Project
Code of Conduct
The text was updated successfully, but these errors were encountered: