29

I'm new to mobile app development and am learning .NET Maui. The app I'm creating needs to listen for Accelerometer events, and send a notification to a web service if the events meet certain criteria. The bit I'm struggling with is how to have the app run in the background, i.e. with no UI visible, without going to sleep, as I'd want the user to close the UI completely. So I'm thinking the app needs to run as some kind of service, with the option to show a UI when needed - how can this be done?

Netricity
  • 2,550
  • 1
  • 22
  • 28
  • 5
    doing activities when the app is backgrounded is highly platform dependent. – Jason Feb 25 '22 at 00:07
  • I'm concentrating on Android for now, and will tackle iOS if this goes well. – Netricity Feb 25 '22 at 09:30
  • 5
    For Android, use a [foreground service](https://developer.android.com/guide/components/foreground-services). The mobile will have a notification and you can open your app when the user taps on it. [Here](https://learn.microsoft.com/en-us/xamarin/android/app-fundamentals/services/foreground-services) is the link with Xamarin, but it is platform code so the same principles apply with maui. – Eli Feb 25 '22 at 11:52
  • Thanks @Elisabeth that looks like the way forward – Netricity Feb 28 '22 at 13:35
  • 3
    @Elisabeth please use the Answer for this and not in the comments. When answers are left in the comments other users will see this question in the search results as not having an accepted answer. – Graham Sep 11 '22 at 19:21

2 Answers2

18

i know it's beign a while but will post an answer for future users!

First we need to understand that background services depends on which platform we use.(thanks Jason) And i will focus on ANDROID, based on Xamarin Documentation (thanks Eli), adapted to Maui.

Since we are working with ANDROID, on MauiProgram we will add the following:

   /// Add dependecy injection to main page
   builder.Services.AddSingleton<MainPage>();
 
#if ANDROID
   builder.Services.AddTransient<IServiceTest, DemoServices>();
#endif

And we create our Interface for DI which provides us the methods to start and stop the foreground service

public interface IServiceTest
{
    void Start();
    void Stop();
}

Then, before platform code we need to add Android Permissions on AndroidManifest.xml:

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

Android Main Activity

public class MainActivity : MauiAppCompatActivity
{
   //set an activity on main application to get the reference on the service
   public static MainActivity ActivityCurrent { get; set; }
   public MainActivity()
   {
      ActivityCurrent = this;
   }
}

And Finally we create our Android foreground service. Check Comments Below. Also on xamarin docs, they show the different properties for notification Builder.

[Service]
public class DemoServices : Service, IServiceTest //we implement our service (IServiceTest) and use Android Native Service Class
{
   public override IBinder OnBind(Intent intent)
   {
      throw new NotImplementedException();
   }
   
   [return: GeneratedEnum]//we catch the actions intents to know the state of the foreground service
   public override StartCommandResult OnStartCommand(Intent intent, [GeneratedEnum] StartCommandFlags flags, int startId)
   {
      if (intent.Action == "START_SERVICE")
      {
         RegisterNotification();//Proceed to notify
      }
      else if (intent.Action == "STOP_SERVICE")
      {
         StopForeground(true);//Stop the service
         StopSelfResult(startId);
      }
      
      return StartCommandResult.NotSticky;
   }
    
   //Start and Stop Intents, set the actions for the MainActivity to get the state of the foreground service
   //Setting one action to start and one action to stop the foreground service
   public void Start()
   {
      Intent startService = new Intent(MainActivity.ActivityCurrent, typeof(DemoServices));
      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);
   }
    
   private void RegisterNotification()
   {
      NotificationChannel channel = new NotificationChannel("ServiceChannel", "ServiceDemo", NotificationImportance.Max);
      NotificationManager manager = (NotificationManager)MainActivity.ActivityCurrent.GetSystemService(Context.NotificationService);
      manager.CreateNotificationChannel(channel);
      Notification notification = new Notification.Builder(this, "ServiceChannel")
         .SetContentTitle("Service Working")
         .SetSmallIcon(Resource.Drawable.abc_ab_share_pack_mtrl_alpha)
         .SetOngoing(true)
         .Build();
    
      StartForeground(100, notification);
   }
}

Now we have our foreground Service working on Android, that show a notification ("Service Working"). Every time it starts. I make a show message foreground service to see it better while testing, in your case it suppose to close the app if that's what you want, but the functioning it's the same.

So having our background service working only left a way to call it so on our main page (as example) i will do the following:

MainPage.xaml

<VerticalStackLayout>
   <Label
      Text="Welcome to .NET Multi-platform App UI"
      FontSize="18"
      HorizontalOptions="Center" />
    
   <Button
      x:Name="CounterBtn"
      Text="start Services"
      Clicked="OnServiceStartClicked"
      HorizontalOptions="Center" />
    
   <Button Text="Stop Service" Clicked="Button_Clicked"></Button>
    
</VerticalStackLayout>

MainPage.xaml.cs

public partial class MainPage : ContentPage
{
   IServiceTest Services;

   public MainPage(IServiceTest Services_)
   {
      InitializeComponent();
      ToggleAccelerometer();
      Services = Services_;
   }

   //method to start manually foreground service
   private void OnServiceStartClicked(object sender, EventArgs e)
   {
      Services.Start();
   }

   //method to stop manually foreground service
   private void Button_Clicked(object sender, EventArgs e)
   {
      Services.Stop();
   }
   
   //method to work with accelerometer
   public void ToggleAccelerometer()
   {
      if (Accelerometer.Default.IsSupported)
      {
         if (!Accelerometer.Default.IsMonitoring)
         {
            Accelerometer.Default.ReadingChanged += Accelerometer_ReadingChanged;
            Accelerometer.Default.Start(SensorSpeed.UI);
         }
         else
         {
            Accelerometer.Default.Stop();
            Accelerometer.Default.ReadingChanged -= Accelerometer_ReadingChanged;
         }
      }
   }
   
   //on accelerometer property change we call our service and it would send a message
   private void Accelerometer_ReadingChanged(object sender, AccelerometerChangedEventArgs e)
   {
      Services.Start(); //this will never stop until we made some logic here
   }
}

It's a long Answer and it would be great to have more official documentation about this! Hope it helps! If anyone can provide more info about IOS, Windows, MacCatalyst would be awesome!

Juan Pablo Gomez
  • 5,203
  • 11
  • 55
  • 101
Leandro Toloza
  • 1,655
  • 1
  • 6
  • 24
3

My rep is too low to add comments

To add on to Leandro's answer, you must specify the usings in the correct platform OS. Otherwise, you will not be able to use intellisense to add the usings.

Click the Android OS from the dropdown shown in this image: Android OS Dropdown Selection

Now you can use intellisense to add the usings.

using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
BRAHN
  • 45
  • 6