'Running a periodic service/ task/ function in mobile development on an android device

I am trying to collect location data from my own android phone for a project, but so far I'm not successful in letting functions run in the background.

I have tried the following:

  • Run repeated functions OnSleep()
    • This process automatically stopped after a few minutes
  • Use a Foreground Service with a repeated function inside of it

This is what I attempted with my Foreground Service:

AndroidManifest.xml

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

MainActivity.cs

[Activity(Label = "LocationApp", Icon = "@mipmap/icon", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize)]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
    public static Activity ActivityCurrent { get; private set;}
    protected override void OnCreate(Bundle savedInstanceState)
    {
        ActivityCurrent = this;
        base.OnCreate(savedInstanceState);

        Xamarin.Essentials.Platform.Init(this, savedInstanceState);
        global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
        LoadApplication(new App());
        RequestPermissions();
    }
    private async void RequestPermissions()
    {
        await CrossPermissions.Current.RequestPermissionAsync<StoragePermission>();
        await CrossPermissions.Current.RequestPermissionAsync<LocationAlwaysPermission>();
    }

    public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
    {
        PermissionsImplementation.Current.OnRequestPermissionsResult(requestCode, permissions, grantResults);
        base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
    }

}

TracingServices.cs

[assembly:Xamarin.Forms.Dependency(typeof(LocationApp.Droid.TracingServices))]
namespace LocationApp.Droid
{
    [Service(ForegroundServiceType = Android.Content.PM.ForegroundService.TypeDataSync)]
    public class TracingServices : Service,ITracingServices
    {
        private List<Timer> Timerlist = new List<Timer>();
        private double initialLon;
        private double initialLat;
        public override IBinder OnBind(Intent intent)
        {
            throw new NotImplementedException();
        }
        [return: GeneratedEnum]
        public override StartCommandResult OnStartCommand(Intent intent, [GeneratedEnum] StartCommandFlags flags, int startId)
        {
            if(intent.Action=="START_SERVICE")
            {
                Console.WriteLine("Started service");
                RegisterNotification();
                TrackLocation();
            }
            else if(intent.Action=="STOP_SERVICE")
            {
                Console.WriteLine("Stopping service");
                StopForeground(true);
                StopSelfResult(startId);
            }
            return StartCommandResult.NotSticky;
        }

        public void Start()
        {
            Intent startService = new Intent(MainActivity.ActivityCurrent, typeof(TracingServices));
            startService.SetAction("START_SERVICE");
            MainActivity.ActivityCurrent.StartService(startService);
        }

        public void Stop()
        {
            Intent stopIntent = new Intent(MainActivity.ActivityCurrent, this.Class);
            stopIntent.SetAction("STOP_SERVICE");
            MainActivity.ActivityCurrent.StartService(stopIntent);
            // stop the timer
            foreach (Timer timer in Timerlist)
            {
                timer.Stop();
            }
        }

        private void RegisterNotification()
        {
            NotificationChannel channel = new NotificationChannel("Servicechannel", "Service demo", NotificationImportance.Max);
            NotificationManager manager = (NotificationManager)MainActivity.ActivityCurrent.GetSystemService(Context.NotificationService);
            manager.CreateNotificationChannel(channel);
            Notification notification = new Notification.Builder(this, "Servicechannel")
                .SetContentTitle("Testing service")
                .SetOngoing(true)
                .Build();

            StartForeground(100, notification);
        }

        private async void TrackLocation()
        {
            // track initial coordinates
            Location result = await Geolocation.GetLocationAsync();
            initialLat = result.Latitude;
            initialLon = result.Longitude;
            // set timer to launch every 3 minutes
            Timer timer = new Timer(3 * 60 * 1000);
            timer.Elapsed += (sender, e) => OnTimedEvent(sender, e, initialLat, initialLon);
            timer.AutoReset = true;
            timer.Enabled = true;
            Timerlist.Add(timer);
        }

        private static async void OnTimedEvent(object source, ElapsedEventArgs e, double lat, double lon)
        {
            Location result = await Geolocation.GetLocationAsync();
            Console.WriteLine($"Final stuff: {result.Latitude},{result.Longitude},{result.Timestamp.DateTime:dd/MM},{result.Timestamp.DateTime:HH:mm}");

            await App.Database.SaveLocationAsync(new LocationData
            {
                Latitude = result.Latitude - lat,
                Longitude = result.Longitude - lon,
                Day = result.Timestamp.DateTime.ToString("dd/MM"),
                Time = result.Timestamp.DateTime.ToString("HH:mm")
            });
        }
    }
}

MainPage.xaml.cs function

async private void ToggleTracking(object send, EventArgs args)
{
    var status = await SecureStorage.GetAsync("Tracking");

    // if the status starts as false, set it to true, update the button text and execute the Foreground service
    if (status != "true")
    {
        await SecureStorage.SetAsync("Tracking", "true");
        ToggleButton.Text = "Stop tracking";
        DependencyService.Get<ITracingServices>().Start();
    }
    // if the status starts as true, set it to false, update the button text and end the Foreground service
    else
    {
    await SecureStorage.SetAsync("Tracking", "false");
    ToggleButton.Text = "Start tracking";
    DependencyService.Get<ITracingServices>().Stop();
    }
}

I am sure the ToggleTracking function is called, as my device displays the Foreground Service as being active, but the function isn't being executed.

Is it possible to edit the Foreground Service so that it still runs the function with an interval or is there a different option that can run functions at an interval in the background?



Solution 1:[1]

Changing the order of operations in the TracingServices class has caused the application to be called, though not with regular interval. Sometimes it executes, sometimes it doesn't for a few intervals. This is how I changed the order:

[Service(ForegroundServiceType = Android.Content.PM.ForegroundService.TypeDataSync)]
    public class TracingServices : Service,ITracingServices
    {
        private List<Timer> Timerlist = new List<Timer>();
        private double initialLon;
        private double initialLat;
        public override IBinder OnBind(Intent intent)
        {
            throw new NotImplementedException();
        }
        [return: GeneratedEnum]
        public override StartCommandResult OnStartCommand(Intent intent, [GeneratedEnum] StartCommandFlags flags, int startId)
        {
            if(intent.Action=="START_SERVICE")
            {
                Console.WriteLine("Started service");
                RegisterNotification();
            }
            else if(intent.Action=="STOP_SERVICE")
            {
                Console.WriteLine("Stopping service");
                StopForeground(true);
                StopSelfResult(startId);
            }
            return StartCommandResult.NotSticky;
        }

        public void Start()
        {
            Intent startService = new Intent(MainActivity.ActivityCurrent, typeof(TracingServices));
            startService.SetAction("START_SERVICE");
            MainActivity.ActivityCurrent.StartService(startService);
        }

        public void Stop()
        {
            Intent stopIntent = new Intent(MainActivity.ActivityCurrent, this.Class);
            stopIntent.SetAction("STOP_SERVICE");
            MainActivity.ActivityCurrent.StartService(stopIntent);
            // stop the timer
            foreach (Timer timer in Timerlist)
            {
                timer.Stop();
            }
        }

        private void RegisterNotification()
        {
            NotificationChannel channel = new NotificationChannel("Servicechannel", "Service demo", NotificationImportance.Max);
            NotificationManager manager = (NotificationManager)MainActivity.ActivityCurrent.GetSystemService(Context.NotificationService);
            manager.CreateNotificationChannel(channel);
            Notification notification = new Notification.Builder(this, "Servicechannel")
                .SetContentTitle("Testing service")
                .SetOngoing(true)
                .Build();

            StartForeground(100, notification);

            TrackLocation();
        }

        private async void TrackLocation()
        {
            Console.WriteLine("Tracking called");
            // track initial coordinates
            Location result = await Geolocation.GetLocationAsync();
            initialLat = result.Latitude;
            initialLon = result.Longitude;
            // set timer to launch every 3 minutes
            Timer timer = new Timer(3 * 60 * 1000);
            timer.Elapsed += (sender, e) => OnTimedEvent(sender, e, initialLat, initialLon);
            timer.AutoReset = true;
            timer.Enabled = true;
            Console.WriteLine("Timer set");
            Timerlist.Add(timer);
        }

        private static async void OnTimedEvent(object source, ElapsedEventArgs e, double lat, double lon)
        {
            Location result = await Geolocation.GetLocationAsync();
            Console.WriteLine($"Final stuff: {result.Latitude},{result.Longitude},{result.Timestamp.DateTime:dd/MM},{result.Timestamp.DateTime:HH:mm}");

            await App.Database.SaveLocationAsync(new LocationData
            {
                Latitude = result.Latitude - lat,
                Longitude = result.Longitude - lon,
                Day = result.Timestamp.DateTime.ToString("dd/MM"),
                Time = result.Timestamp.DateTime.ToString("HH:mm")
            });
        }
    }

If there is a way for the function to fire every time at an interval, I'd love to hear it.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Unseptium