Jacob Ernst
Jacob Ernst

Reputation: 107

Xamarin forms background tasks run only when app is open on ios

  1. Expected behavior
    • ios
      • When the app is minimized and notifications come in every 5 seconds.
    • Android
      • When the app is minimized and notifications come in every 5 seconds.
  2. Actual behavior
    • ios
      • App minimizes and no notifications come in, opening the app causes notifications to come in every 5 seconds.
    • Android
      • When the app is minimized and notifications come in every 5 seconds.
        //from app.xaml.cs
        protected override void OnSleep()
            {
                void ScheduleNotification()
                {
                    // Start a timer that runs after 5 seconds.
                    Device.StartTimer(TimeSpan.FromSeconds(5), () =>
                    {
                        System.Threading.Tasks.Task.Factory.StartNew( () =>
                        {
                            // Do the actual request and wait for it to finish.
                             PerformNotification();
                            // Switch back to the UI thread to update the UI
                            Device.BeginInvokeOnMainThread(() =>
                            {
                                // Update the UI
                                // ...
                                // Now repeat by scheduling a new request
                                ScheduleNotification();
                                count++;
                            });
                        }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
        
                        // Don't repeat the timer (we will start a new timer when the request is finished)
                        return false;
                    });
                }
               void PerformNotification()
                {
                    CrossLocalNotifications.Current.Show("title", "body"+ count.ToString());
        
                }
                ScheduleNotification();
            }
        

Upvotes: 4

Views: 9226

Answers (1)

user2074945
user2074945

Reputation: 547

On iOS, apps that don't request to be run in the Background, can not run in the Background for longer than 10 seconds. In order to make some code run in the Background you could implement a DependencyService on iOS and on android. You would put the following code inside that service on iOS:

public async Task RunCodeInBackgroundMode(Func<Task> action, string name = "MyBackgroundTaskName")
    {
        nint taskId = 0;
        var taskEnded = false;
        taskId = UIApplication.SharedApplication.BeginBackgroundTask(name, () =>
        {
            //when time is up and task has not finished, call this method to finish the task to prevent the app from being terminated
            Console.WriteLine($"Background task '{name}' got killed");
            taskEnded = true;
            UIApplication.SharedApplication.EndBackgroundTask(taskId);
        });
        await Task.Factory.StartNew(async () =>
        {
            //here we run the actual task
            Console.WriteLine($"Background task '{name}' started");
            await action();
            taskEnded = true;
            UIApplication.SharedApplication.EndBackgroundTask(taskId);
            Console.WriteLine($"Background task '{name}' finished");
        });

        await Task.Factory.StartNew(async () =>
        {
            //Just a method that logs how much time we have remaining. Usually a background task has around 180 seconds to complete. 
            while (!taskEnded)
            {
                Console.WriteLine($"Background task '{name}' time remaining: {UIApplication.SharedApplication.BackgroundTimeRemaining}");
                await Task.Delay(1000);
            }
        });
    }

On Android you could use the following code:

public async Task RunCodeInBackgroundMode(Func<Task> action, string name = "MyBackgroundTaskName")
        {
            var powerManager = (PowerManager)Application.Context.GetSystemService(Context.PowerService);
            var wakeLock = powerManager.NewWakeLock(WakeLockFlags.Partial,
                                                    name);
            //acquire a partial wakelock. This prevents the phone from going to sleep as long as it is not released.
            wakeLock.Acquire();
            var taskEnded = false;

            await Task.Factory.StartNew(async () =>
            {
                //here we run the actual code
                Console.WriteLine($"Background task '{name}' started");
                await action();
                Console.WriteLine($"Background task '{name}' finished");
                wakeLock.Release();
                taskEnded = true;
            });

            await Task.Factory.StartNew(async () =>
            {
                //just a method to keep track of how long the task runs
                var stopwatch = new Stopwatch();
                stopwatch.Start();
                while (!taskEnded)
                {
                    Console.WriteLine($"Background '{name}' task with wakelock still running ({stopwatch.Elapsed.TotalSeconds} seconds)");
                    await Task.Delay(1000);
                }
                stopwatch.Stop();
            });
        }

Inside the shared project, you could call those methods in the following way:

var deviceInfoServic = ServiceLocator.Instance.Get<YourService Interface here>();
await deviceInfoServic.RunCodeInBackgroundMode(async () =>
{
    //your code here
});

Upvotes: 3

Related Questions