dotnet / maui

.NET MAUI is the .NET Multi-platform App UI, a framework for building native device applications spanning mobile, tablet, and desktop.
https://dot.net/maui
MIT License
22.01k stars 1.73k forks source link

Problems with .NET Maui Hybrid Blazor Foreground Service #20303

Closed JonathanTheronnn closed 7 months ago

JonathanTheronnn commented 7 months ago

Description

Hi there,

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

Steps to Reproduce

  1. Create Maui Blazor Hybrid App
  2. Use Code Snippets above to replicate and check if you can re-enter the app after closing it completely running the foreground service

Link to public reproduction project repository

No response

Version with bug

8.0.6

Is this a regression from previous behavior?

Yes, this used to work in .NET MAUI

Last version that worked well

Unknown/Other

Affected platforms

Android

Affected platform versions

No response

Did you find any workaround?

No workaround yet

Relevant log output

No response

rmarinho commented 7 months ago

Hello @JonathanTheronnn does this work if you clear up all that code on the OnCreate ?

thanks

ghost commented 7 months ago

Hi @JonathanTheronnn. We have added the "s/needs-info" label to this issue, which indicates that we have an open question for you before we can take further action. This issue will be closed automatically in 7 days if we do not hear back from you by then - please feel free to re-open it if you come back to this issue after that time.

ghost commented 7 months ago

Hi @JonathanTheronnn. We have added the "s/needs-repro" label to this issue, which indicates that we require steps and sample code to reproduce the issue before we can take further action. Please try to create a minimal sample project/solution or code samples which reproduce the issue, ideally as a GitHub repo that we can clone. See more details about creating repros here: https://github.com/dotnet/maui/blob/main/.github/repro.md

This issue will be closed automatically in 7 days if we do not hear back from you by then - please feel free to re-open it if you come back to this issue after that time.

ghost commented 7 months ago

This issue has been automatically marked as stale because it has been marked as requiring author feedback but has not had any activity for 4 days. It will be closed if no further activity occurs within 3 days of this comment. If it is closed, feel free to comment when you are able to provide the additional information and we will re-investigate.

ghost commented 7 months ago

This issue has been automatically marked as stale because it has been marked as requiring author feedback to reproduce the issue but has not had any activity for 4 days. It will be closed if no further activity occurs within 3 days of this comment. If it is closed, feel free to comment when you are able to provide the additional information and we will re-investigate.

Eilon commented 7 months ago

I think this could be the same as https://github.com/dotnet/maui/issues/13529 and/or https://github.com/dotnet/maui/issues/17477.

ghost commented 7 months ago

This issue has been automatically marked as stale because it has been marked as requiring author feedback but has not had any activity for 4 days. It will be closed if no further activity occurs within 3 days of this comment. If it is closed, feel free to comment when you are able to provide the additional information and we will re-investigate.

ghost commented 7 months ago

This issue has been automatically marked as stale because it has been marked as requiring author feedback to reproduce the issue but has not had any activity for 4 days. It will be closed if no further activity occurs within 3 days of this comment. If it is closed, feel free to comment when you are able to provide the additional information and we will re-investigate.

johngraumann commented 7 months ago

We are seeing the same issue in our app as described in the original issue.

  1. Start app and foreground service.
  2. Swipe MainActivity away. Foreground service remains running.
  3. Open app again.

Result: Hangs at the splash screen. This is on Android 13.