{"id":9845,"date":"2023-01-12T21:21:41","date_gmt":"2023-01-12T21:21:41","guid":{"rendered":"http:\/\/putridparrot.com\/blog\/?p=9845"},"modified":"2023-09-19T15:53:52","modified_gmt":"2023-09-19T15:53:52","slug":"android-foreground-service-using-maui","status":"publish","type":"post","link":"https:\/\/putridparrot.com\/blog\/android-foreground-service-using-maui\/","title":{"rendered":"Android Foreground Service using MAUI"},"content":{"rendered":"<p>In some situations you&#8217;ll want to keep some part of your application running even if the main application goes to sleep. For example the Android Clock applet when started will keep running even if you essentially close the Clock applet. It will display an icon in the status bar at the top right of the Android device and also include notifications.<\/p>\n<p><em>Note: Code for this solution is available via my <a href=\"https:\/\/github.com\/putridparrot\/blog-projects\/tree\/master\/MauiAndroidForegroundService\" rel=\"noopener\" target=\"_blank\">blog projects<\/a> repository.<\/em><\/p>\n<p><strong>Getting Started<\/strong><\/p>\n<ul>\n<li>Create a MAUI application<\/li>\n<li>We&#8217;ll add the package CommunityToolkit.Mvvm just so we can use the <em>WeakReferenceMessenger<\/em> to send messages to our platform specific code. We could ofcourse do this using an interface and register platform specific implementations of the interface, amongst other ways. But for simplicity here, we&#8217;ll pass messages around<\/li>\n<li>Add the MessageData record\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\npublic record MessageData(string Message, bool Start);\r\n<\/pre>\n<p>This will just be used in the IMessenger to pass commands to our platform specific code.\n<\/li>\n<li>In MauiProgram.cs before <em>builder.Build()<\/em> add the following code\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\nbuilder.Services.AddSingleton&lt;MainPage&gt;();\r\nbuilder.Services.AddSingleton&lt;IMessenger, WeakReferenceMessenger&gt;();\r\n<\/pre>\n<\/li>\n<li>We&#8217;re going to simply use code-behind (again to keep things simple), so in the MainPage.xaml, within the ContentPage content, add the following\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\n&lt;VerticalStackLayout&gt;\r\n  &lt;Entry x:Name=&quot;Input&quot; Margin=&quot;10&quot; \/&gt;\r\n  &lt;Button Text=&quot;Start Service&quot; Clicked=&quot;Start_OnClicked&quot; Margin=&quot;10&quot; \/&gt;\r\n  &lt;Button Text=&quot;Stop Service&quot; Clicked=&quot;Stop_OnClicked&quot; Margin=&quot;10&quot; \/&gt;\r\n&lt;\/VerticalStackLayout&gt;\r\n<\/pre>\n<\/li>\n<li>Now in the code behind MainPage.xaml.cs add the following code\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\nprivate readonly IMessenger _messenger;\r\n\r\npublic MainPage(IMessenger messenger)\r\n{\r\n   InitializeComponent();\r\n\r\n   _messenger = messenger;\r\n}\r\n\r\nprivate void Start_OnClicked(object sender, EventArgs e)\r\n{\r\n   _messenger.Send(new MessageData(Input.Text, true));\r\n}\r\n\r\nprivate void Stop_OnClicked(object sender, EventArgs e)\r\n{\r\n   _messenger.Send(new MessageData(Input.Text, false));\r\n}\r\n<\/pre>\n<\/li>\n<\/ul>\n<p>At this point we have a basic test application and a way to start\/stop the foreground service.<\/p>\n<p><strong>Android platform specific code<\/strong><\/p>\n<p>The first thing we&#8217;ll need to do is edit the Platforms\/Android\/MainApplication.cs file. We need to start by adding a notification channel which will be displayed in the statusbar and act as the interaction point with our foreground service. <\/p>\n<p>The first thing we need is a channel id, this is a unique identifier within our application to denote the notification channel we&#8217;ll want to communicate with. It&#8217;s possible for us to have multiple notification channels, but in this instance we&#8217;ll just use the one. So in MainApplication add<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\npublic static readonly string ChannelId = &quot;exampleServiceChannel&quot;;\r\n<\/pre>\n<p>Next, override OnCreate so it looks like this<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\npublic override void OnCreate()\r\n{\r\n  base.OnCreate();\r\n\r\n  if (Build.VERSION.SdkInt &gt;= BuildVersionCodes.O)\r\n  {\r\n#pragma warning disable CA1416\r\n    var serviceChannel =\r\n       new NotificationChannel(ChannelId, &quot;Example Service Channel&quot;, NotificationImportance.Default);\r\n\r\n       if (GetSystemService(NotificationService) is NotificationManager manager)\r\n       {\r\n          manager.CreateNotificationChannel(serviceChannel);\r\n       }\r\n#pragma warning restore CA1416\r\n   }\r\n}\r\n<\/pre>\n<p>As you can see, this code is targeting API >= 21 (Oreo). We create a <em>NotificationChannel<\/em> with the <em>ChannelId<\/em> we declare earlier. The notification has a name &#8220;Example Service Channel&#8221; and an importance level. This needs to be set to <em>Low<\/em> or higher. If we set it anything above <em>Low<\/em> we&#8217;ll hear a sound when the notification is sent\/updated. Whilst the user can disable this, you may or may not prefer a low importance so your service is less intrusive. Ofcourse it&#8217;s all dependent upon your requirements.<\/p>\n<p>Before we get to the foreground service itself, let&#8217;s update the MainActivity.cs to handler our <em>IMessenger<\/em> messages from the UI.<\/p>\n<p>In the MainActivity constructor add the following<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\nvar messenger = MauiApplication.Current.Services.GetService&lt;IMessenger&gt;();\r\n\r\nmessenger.Register&lt;MessageData&gt;(this, (recipient, message) =&gt;\r\n{\r\n  if (message.Start)\r\n  {\r\n    StartService(message.Message);\r\n  }\r\n  else\r\n  {\r\n    StopService();\r\n  }\r\n});\r\n<\/pre>\n<p>This is just some plumbing code, basically when we press the start service button we&#8217;ll receive a message with <em>Start<\/em> as true and a message from our entry control. Obviously if we get start true we&#8217;ll call StartService and if not, we&#8217;ll call StopService. <\/p>\n<p>Here&#8217;s the start and stop service code<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\nprivate void StartService(string input)\r\n{\r\n  var serviceIntent = new Intent(this, typeof(ExampleService));\r\n  serviceIntent.PutExtra(&quot;inputExtra&quot;, input);\r\n\r\n  StartService(serviceIntent);\r\n}\r\n\r\nprivate void StopService()\r\n{\r\n  var serviceIntent = new Intent(this, typeof(ExampleService));\r\n  StopService(serviceIntent);\r\n}\r\n<\/pre>\n<p>Start service is passed the entry text and creates an intent associated with our service code (ExampleService). We add a key\/value in the <em>PutExtra<\/em> method. The key is whatever we want and obviously the value is whatever we want to associated with the key. This will be handled via our service, so the key would best be a const in a real world application.<\/p>\n<p>The <em>StartService(serviceIntent)<\/em> call is used on an activity that is already in the foreground &#8211; so in our case to be able to press the button the application must be in the foreground. If your application might be in the background you can call <em>StartForegroundService(serviceIntent)<\/em> to both force the application into the foreground and start the service.<\/p>\n<p>Two more things before we get to the service code. The service will require a small icon, so create the folder Platforms\/Android\/Resources\/drawable and add a .PNG into this. Mine&#8217;s named AppIcon.png as it&#8217;s a duplicate of the actual application icon.<\/p>\n<p>Now we need to declare the requirement of the user permission FOREGROUND_SERVICE, so edit the AndroindManifest.xml file and add the following within the manifest section<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\n&lt;uses-permission android:name=&quot;android.permission.FOREGROUND_SERVICE&quot;\/&gt;\r\n<\/pre>\n<p><strong>The Service code<\/strong><\/p>\n<p>To recap, at this point we have a basic user interface, which allows us to start and stop a service. We have a notification channel created along with the code for starting and stopping a service. We now need the service. Mine&#8217;s named ExampleService.cs and is within the Platforms\/Android folder alongside the MainActivity.cs etc.<\/p>\n<p>Create a basic service with the following code<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\n&#x5B;Service]\r\ninternal class ExampleService : Service\r\n{\r\n    public override IBinder OnBind(Intent intent)\r\n    {\r\n        return null;\r\n    }\r\n}\r\n<\/pre>\n<p>and here&#8217;s the usings, just in case you need to check<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\nusing Android.App;\r\nusing Android.Content;\r\nusing Android.OS;\r\nusing AndroidX.Core.App;\r\n<\/pre>\n<p>We&#8217;re not using the OnBind method which is used for &#8220;Bound Services&#8221; so simply return null here. We&#8217;ll also need the <em>Service<\/em> attribute on the class to register our service.<\/p>\n<p>We now need to override the OnStartCommand which will get information for the key\/value we added in the MainActivity (via PutExtra). We&#8217;ll also want to create an Intent back to our MainActivity so when the user clicks on the notification it can bring the application back into the foreground. Hence we create the <em>notificationIntent<\/em>. <\/p>\n<p>The <em>NotificationCompat.Builder<\/em> needs to use the same channel id that we want to send messages to and it requires a PendingIntent within the builder. In the builder we <strong>must<\/strong> supply the icon image via <em>SetSmallIcon<\/em> and we&#8217;ll need the content title set. You can see we&#8217;re sending the message to the <em>SetContentText<\/em>. <\/p>\n<p>Finally, unless we&#8217;re aiming to reuse this notification we call <em>Build<\/em> on it and pass this along with an id to the <em>StartForeground<\/em>.<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\npublic override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId)\r\n{\r\n  var input = intent.GetStringExtra(&quot;inputExtra&quot;);\r\n\r\n  var notificationIntent = new Intent(this, typeof(MainActivity));\r\n  var pendingIntent = PendingIntent.GetActivity(this, 0, notificationIntent, 0);\r\n\r\n  var notification = new NotificationCompat.Builder(this, MainApplication.ChannelId)\r\n     .SetContentTitle(&quot;Example Service&quot;)\r\n     .SetContentText(input)\r\n     .SetSmallIcon(Resource.Drawable.AppIcon)\r\n     .SetContentIntent(pendingIntent)\r\n     .Build();\r\n\r\n  StartForeground(1, notification);\r\n\r\n  return StartCommandResult.NotSticky;\r\n}\r\n<\/pre>\n<p>If we are intending to send multiple messages you would want to create the notification (without the <em>Build<\/em> call) and reuse this to update a notification channel, hence you&#8217;d call <em>Build<\/em> within <em>StartForeground<\/em> code, like this<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\nvar notification = new NotificationCompat.Builder(this, MainApplication.ChannelId)\r\n     .SetContentTitle(&quot;Example Service&quot;)\r\n     .SetContentText(input)\r\n     .SetSmallIcon(Resource.Drawable.AppIcon)\r\n     .SetContentIntent(pendingIntent)\r\n\r\nStartForeground(1, notification.Build());\r\n<\/pre>\n<p>If we imagine that after <em>StartForeground<\/em> we start some task, for example downloading a file, then remember this should be run on a background thread and it could then update the notification before again calling the <em>StartForeground(1, notification.Build())<\/em> code, ofcourse assuming you&#8217;re running the process from the service.<\/p>\n<p>Finally this method returns <em>StartCommandResult.NotSticky<\/em>. This tells the OS to not bother recreating the application if (for example) the device runs out of memory. <em>StartCommandResult.Sticky<\/em> is used to tell the OS to recreate the service when it has enough memory. There&#8217;s also the option <em>StartCommandResult.RedeliverIntent<\/em> which is like the <em>NotSticky<\/em> but if the service is killed before calling <em>stopSelf()<\/em> for a given intent then the intent will be re-delivered until it completes.<\/p>\n<p><strong>References<\/strong><\/p>\n<p>This code presented here is based on <a href=\"https:\/\/www.youtube.com\/watch?v=FbpD5RZtbCc\" rel=\"noopener\" target=\"_blank\">How to Start a Foreground Service in Android (With Notification Channels)<\/a> but translated to C# and MAUI code.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In some situations you&#8217;ll want to keep some part of your application running even if the main application goes to sleep. For example the Android Clock applet when started will keep running even if you essentially close the Clock applet. It will display an icon in the status bar at the top right of the [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[120,330],"tags":[],"class_list":["post-9845","post","type-post","status-publish","format-standard","hentry","category-android","category-maui"],"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts\/9845","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/comments?post=9845"}],"version-history":[{"count":5,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts\/9845\/revisions"}],"predecessor-version":[{"id":10096,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/posts\/9845\/revisions\/10096"}],"wp:attachment":[{"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/media?parent=9845"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/categories?post=9845"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/putridparrot.com\/blog\/wp-json\/wp\/v2\/tags?post=9845"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}