I am struggling setting up a Foreground Service on .NET Maui Blazor Hybrid (please take note not normal Maui). Everything works as expected when the Android App runs, it works in the background etc UNTIL the Application is quitted entirely (and only the service runs).
When a user re-enters the app from the Foreground Service Notification or normal click on the icon of the app, the app runs the "OnCreate" method but then hangs on the splash screen.
Please see the code snippets below (please take note that this code did work in normal .NET Maui Application
MainActivity.cs
public class MainActivity : MauiAppCompatActivity
{
public static MainActivity context;
Intent ServiceIntent;
private IFusedLocationProviderClient _locationProviderClient;
public LocationDataService _locationDataService;
public GlobalsService _globalsService;
private AndroidLocationService _locationService;
private Location PreviousLocation;
private LocationCallbackHelper locationCallback;
[Obsolete]
protected override void OnCreate(Bundle? savedInstanceState)
{
try
{
base.OnCreate(savedInstanceState);
ServiceIntent = new Intent(this, typeof(AndroidLocationService));
_globalsService = IPlatformApplication.Current?.Services.GetService<GlobalsService>();
_locationDataService = IPlatformApplication.Current?.Services.GetService<LocationDataService>();
Platform.Init(this, savedInstanceState);
InitializeLocationService();
SetMessageServices();
context = this;
}
catch (Exception e)
{
throw e;
}
}
public void InitializeLocationService()
{
try
{
_locationProviderClient = LocationServices.GetFusedLocationProviderClient(this);
}
catch (Exception e)
{
throw e;
}
}
private bool IsServiceRunning(System.Type cls)
{
try
{
ActivityManager manager = (ActivityManager)GetSystemService(Context.ActivityService);
foreach (var service in manager.GetRunningServices(int.MaxValue))
{
if (service.Service.ClassName.Equals(Java.Lang.Class.FromType(cls).CanonicalName))
{
return true;
}
}
return false;
}
catch (Exception E)
{
throw E;
}
}
public void SetMessageServices()
{
WeakReferenceMessenger.Default.Register<StartTrackingMessage>(this, (recipient, message) =>
{
if (!IsServiceRunning(typeof(AndroidLocationService)))
{
if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
{
StartForegroundService(ServiceIntent);
}
else
{
StartService(ServiceIntent);
}
StartLocationUpdates();
}
});
WeakReferenceMessenger.Default.Register<StopTrackingMessage>(this, (recipient, message) =>
{
if (IsServiceRunning(typeof(AndroidLocationService)))
{
StopService(ServiceIntent);
StopLocationUpdates();
}
});
}
[Obsolete]
public void StartLocationUpdates()
{
var locationRequest = LocationRequest.Create()
.SetPriority(LocationRequest.PriorityHighAccuracy)
.SetInterval(2000) // 2 seconds
.SetFastestInterval(1000);
locationCallback = new LocationCallbackHelper();
locationCallback.LocationResult += (sender, locationResult) =>
{
ProcessLocation(locationResult);
};
_locationProviderClient.RequestLocationUpdates(locationRequest, locationCallback, Looper.MainLooper);
_globalsService.SetTracking(true);
}
public void StopLocationUpdates()
{
_locationProviderClient.RemoveLocationUpdates(locationCallback);
_globalsService.SetTracking(false);
}
private void ProcessLocation(LocationResult locationResult)
{
foreach (var location in locationResult.Locations)
{
if (location != null)
{
double distance = _globalsService.Distance;
if (PreviousLocation != null)
{
distance += Microsoft.Maui.Devices.Sensors.Location.CalculateDistance(PreviousLocation.Latitude, PreviousLocation.Longitude, location.Latitude, location.Longitude, DistanceUnits.Kilometers);
_globalsService.SetDistance(distance);
}
PreviousLocation = location;
_locationDataService.CurrentLocation = new LocationData
{
Latitude = location.Latitude,
Longitude = location.Longitude,
Speed = location.Speed * 3.6,
TotalDistance = distance
};
}
}
}
}
AndroidLocationService.cs
[Service]
public class AndroidLocationService : Service, IAndroidLocationTracker
{
CancellationTokenSource _cts;
public override IBinder? OnBind(Intent? intent)
{
throw new NotImplementedException();
}
[Obsolete]
public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId)
{
_cts = new CancellationTokenSource();
Notification notif = new Notifications().ReturnNotif();
if ((int)Build.VERSION.SdkInt > 26)
{
StartForeground(AndroidContstants.NOTIFICATION_ID, notif);
}
else
{
StartService(intent);
}
return StartCommandResult.Sticky;
}
public override void OnDestroy()
{
try
{
if (_cts != null)
{
_cts.Token.ThrowIfCancellationRequested();
_cts.Cancel();
}
base.OnDestroy();
}
catch (Exception E)
{
throw E;
}
}
public void OnProviderDisabled(string provider)
{
throw new NotImplementedException();
}
public void OnProviderEnabled(string provider)
{
throw new NotImplementedException();
}
public void OnStatusChanged(string? provider, [GeneratedEnum] Availability status, Bundle? extras)
{
throw new NotImplementedException();
}
public void StartTracking()
{
throw new NotImplementedException();
}
public void StopTracking()
{
throw new NotImplementedException();
}
}
Notification.cs
internal class Notifications
{
private static Context context = global::Android.App.Application.Context;
public Notification ReturnNotif()
{
try
{
var intent = new Intent(context, typeof(MainActivity));
intent.AddFlags(ActivityFlags.SingleTop);
intent.PutExtra("Title", "Message");
var pendingIntent = PendingIntent.GetActivity(context, 0, intent, PendingIntentFlags.Immutable);
var notifBuilder = new NotificationCompat.Builder(context, AndroidContstants.CHANNEL_ID)
.SetContentTitle("Floki - SEESA")
.SetContentText("Tracking your location")
.SetSmallIcon(Resource.Drawable.splash)
.SetOngoing(true)
.SetContentIntent(pendingIntent);
if (global::Android.OS.Build.VERSION.SdkInt >= BuildVersionCodes.O)
{
NotificationChannel notificationChannel = new NotificationChannel(AndroidContstants.CHANNEL_ID, "Title", NotificationImportance.High);
notificationChannel.Importance = NotificationImportance.Default;
notificationChannel.EnableLights(true);
notificationChannel.EnableVibration(true);
notificationChannel.SetShowBadge(true);
notificationChannel.SetVibrationPattern(new long[] { 100, 200, 300 });
var notifManager = context.GetSystemService(Context.NotificationService) as NotificationManager;
if (notifManager != null)
{
notifBuilder.SetChannelId(AndroidContstants.CHANNEL_ID);
notifManager.CreateNotificationChannel(notificationChannel);
}
}
return notifBuilder.Build();
}
catch (Exception E)
{
return null;
}
}
}
Weather.razor
@*USINGS*@
@using YES_Mobile_Application.BAL;
@using YES_Mobile_Application.Services;
@using YES_Mobile_Application.Utils;
@using CommunityToolkit.Mvvm.Messaging;
@using static YES_Mobile_Application.Utils.Messages;
@*INJECTIONS*@
@inject NavigationManager NavManager;
@inject LoginService LoginService;
@inject GlobalsService GlobalService;
@inject LocationDataService LocationDataService;
@page "/weather"
<h1>Weather</h1>
<p>This component demonstrates showing data.</p>
<button @onclick="SignOut">Sign Out</button>
<button class="btn btn-success" @onclick="StartTracking">Start Tracking</button>
<button class="btn btn-danger" @onclick="StopTracking">Stop Tracking</button>
<div class="container">
<div class="row">
<div class="col card shadow-sm p-2 m-2">
<p class="fw-bold">Latitude</p>
<p class="text-secondary fs-2">@latitude</p>
</div>
</div>
<div class="row">
<div class="col card shadow-sm p-2 m-2">
<p class="fw-bold">Longitude</p>
<p class="text-secondary fs-2">@longitude</p>
</div>
</div>
<div class="row">
<div class="col card shadow-sm p-2 m-2">
<p class="fw-bold">Speed</p>
<p class="text-secondary fs-2">@speed</p>
</div>
</div>
<div class="row">
<div class="col card shadow-sm p-2 m-2">
<p class="fw-bold">Distance</p>
<p class="text-secondary fs-2">@distance</p>
</div>
</div>
</div>
<style>
button, button:focus, button:active, button:hover {
outline: none !important;
box-shadow: none !important;
}
</style>
@code {
private double latitude;
private double longitude;
private double speed;
private double distance;
private void UpdateLocation(LocationData locationData)
{
latitude = locationData.Latitude;
longitude = locationData.Longitude;
speed = Math.Round(locationData.Speed, 2);
distance = Math.Round(locationData.TotalDistance, 2);
InvokeAsync(StateHasChanged);
}
protected override void OnInitialized()
{
if (!LoginService.IsLoggedIn)
{
NavManager.NavigateTo("/login", replace: true);
}
if (GlobalService.IsTrackingLive)
{
StartTracking();
}
}
private void SignOut()
{
LoginService.LogoutAsync();
if (LoginService.IsLoggedIn)
{
//Todo- continue to main page
NavManager.NavigateTo("weather");
}
else
{
NavManager.NavigateTo("login");
}
}
private void StartTracking()
{
WeakReferenceMessenger.Default.Send(new StartTrackingMessage());
if (GlobalService.IsTrackingLive)
{
LocationDataService.OnLocationChanged += UpdateLocation;
}
}
private void StopTracking()
{
if (GlobalService.IsTrackingLive)
{
WeakReferenceMessenger.Default.Send(new StopTrackingMessage());
LocationDataService.OnLocationChanged -= UpdateLocation;
}
}
}
We also recreated a new project to try replicate the issue (without all the tracking code, just to test the foreground), adn the issue is still there.
We do have the correct permissions in the Manifest file (as far as we are concerned).
I would appreciate any solutions or at least know if this is a problem with Blazor Hybrid
Again, when opened from the foreground service, it should continue with the app and not hang on the splash screen.