Reputation: 2738
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?
Upvotes: 41
Views: 25853
Reputation: 91
To complement @Leandro Toloza answer, implementing a background service on iOS shares similarities with its Android counterpart, albeit with a slightly more straightforward approach:
Start by creating a Service class that will contain the platform-specific code to execute in the background. This class will define the tasks your app needs to perform while running in the background.
public class iOsService
{
nint _taskId;
CancellationTokenSource _cts;
public bool isStarted = false;
public async Task Start()
{
_cts = new CancellationTokenSource();
_taskId = UIApplication.SharedApplication.BeginBackgroundTask("com.company.product.name", OnExpiration);
try
{
var locShared = new Location();
isStarted = true;
await locShared.Run(_cts.Token);
}
catch (OperationCanceledException)
{
}
finally
{
if (_cts.IsCancellationRequested)
{
WeakReferenceMessenger.Default.Send(new ServiceMessage(ActionsEnum.STOP));
}
}
var time = UIApplication.SharedApplication.BackgroundTimeRemaining;
UIApplication.SharedApplication.EndBackgroundTask(_taskId);
}
public void Stop()
{
isStarted = false;
_cts.Cancel();
}
void OnExpiration()
{
UIApplication.SharedApplication.EndBackgroundTask(_taskId);
}
}
Next, configure your AppDelegate to manage the lifecycle of the background service. This involves creating a new instance of the service and implementing the necessary methods to start and stop it.
public partial class AppDelegate : Microsoft.Maui.MauiUIApplicationDelegate
{
private iOsService service;
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
service = new iOsService();
WeakReferenceMessenger.Default.Register<ServiceMessage>(this,
HandleServiceMessage);
// Add any additional setup code here
}
private async void HandleServiceMessage(object recipient, ServiceMessage message)
{
if (message.Value == ActionsEnum.START)
{
if (!service.isStarted)
await service.Start();
}
else
{
if (service.isStarted)
service.Stop();
}
}
}
Lastly, ensure your Info.plist file is properly configured to allow background execution. Add the necessary keys under UIBackgroundModes, specifying the types of background tasks your app supports, such as fetch and processing.
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>processing</string>
<!-- Add any other background modes your app requires -->
</array>
You can find the full example here
Upvotes: 0
Reputation: 2020
I know it's been 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!
Upvotes: 41
Reputation: 105
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;
Upvotes: 9