Android notifications using MAUI (Part 1 of 10)

In my previous post Android Foreground Service using MAUI we looked at implementing a foreground service for Android using MAUI. We touched on notification channels and notifications in general but I decided to go a bit deeper into learning about what we can do with notifications…

I found a good way to learn what Android had to offer was by going through the excellent set of tutorials on the “Coding in Flow” channel on youtube (starting with Notifications Tutorial Part 1 – NOTIFICATION CHANNELS – Android Studio Tutorial).

We’re going to follow through those tutorials in the this and the next nine posts. The intention is to recreate the tutorials but using MAUI and C#, however I know (as I’ve already tried the code out) that there are several areas I was not able to get things to work 100% the same way as the tutorials. Now, this may be due to my code or the current lack of support in MAUI or simply that things have changed in the four years since those tutorials were posted – anyway so upfront, I had problems with some of the notification styles and also with the large icon – if I do figure a way to implement the same functionality I will update this and the subsequent post.

Getting Started

  • Create a MAUI application
  • We’ll add the package CommunityToolkit.Mvvm just so we can use the WeakReferenceMessenger 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’ll pass messages around
  • Add the MessageData record
    public record MessageData(int Channel, string Title, string Message);
    

    This will just be used in the IMessenger to pass commands to our platform specific code.

  • In MauiProgram.cs before builder.Build() add the following code
    builder.Services.AddSingleton<MainPage>();
    builder.Services.AddSingleton<IMessenger, WeakReferenceMessenger>();
    
  • We’re going to use code-behind (again to keep things simple), so in the MainPage.xaml, within the ContentPage content, add the following
    <VerticalStackLayout>
       <Entry Text="Title" x:Name="Title" Margin="10" />
       <Entry Text="Message" x:Name="Message" Margin="10" />
    
       <Button Text="Send on Channel 1" Clicked="Channel1_OnClicked" Margin="10" />
       <Button Text="Send on Channel 2" Clicked="Channel2_OnClicked" Margin="10" />
    </VerticalStackLayout>
  • Now in the code behind MainPage.xaml.cs add the following code
    private readonly IMessenger _messenger;
    
    public MainPage(IMessenger messenger)
    {
       InitializeComponent();
    
       _messenger = messenger;
    }
    
    private void Channel1_OnClicked(object sender, EventArgs e)
    {
       _messenger.Send(new MessageData(1, Title.Text, Message.Text));
    }
    
    private void Channel2_OnClicked(object sender, EventArgs e)
    {
       _messenger.Send(new MessageData(2, Title.Text, Message.Text));
    }
    

At this point we have a basic test application with ways to enter a title, a message and send to notification channel 1 and/or channel 2 (we’ll discuss these in the next section).

Android platform specific code

So the MAUI shared code is complete, but now we need to start writing code specific to the Android platform. Note that there are alternatives to writing the code (as already suggests) such as creating platform specific services, but for this post we’re going to do things as simple as possible and reduce things (hopefully) to the bare minimum.

Go to the Platforms/Android folder and in MainApplication.cs add the OnCreate method and again for simplicity, we’ll just have this method set up our channels. So our code looks like this

public override void OnCreate()
{
  base.OnCreate();

  if (Build.VERSION.SdkInt >= BuildVersionCodes.O)
  {
#pragma warning disable CA1416
    var channel1 = new NotificationChannel(Channel1Id, "Channel 1", NotificationImportance.High)
    {
      Description = "This is Channel 1"
    };

    var channel2 = new NotificationChannel(Channel2Id, "Channel 2", NotificationImportance.Low)
    {
      Description = "This is Channel 2"
    };

    if (GetSystemService(NotificationService) is NotificationManager manager)
    {
      manager.CreateNotificationChannel(channel1);
      manager.CreateNotificationChannel(channel2);
    }
#pragma warning restore CA1416
  }
}

We’ll also need to add the const values for the channel id’s, so add the following to the MainApplication class

public static readonly string Channel1Id = "channel1";
public static readonly string Channel2Id = "channel2";

These are public to make them accessible from our MainActivity, in a real world application you might prefer to have these in a separate class, but we’re not going to worry too much about such things here.

With the OnCreate method we need to check that we’re using API >= 21 (or Oreo) to use notification channels and I’m just disabling warnings from the compiler with the pragma’s.

We create two channels, the first we give a high importance, which means that by default when it receives a message it will make a sound and popup the message, unless the user changes their settings. If the user does change the settings, either they need to be reinstated or you need to uninstall and reinstall the application to get them back to default – you may find yourself doing this through the post to reset the application in the emulator.

The code to create the notification channels is fairly simple and hopefully self-explanatory.

Now, you don’t need to create the channels at start-up. If your application (as the one I’m working on that prompted the necessity to learn this stuff) only needs the channels when it goes into the background or when a button is clicked, then you can create the channels as and when required.

Finally for this section, we need to amend the MainActivity.cs to both receive messages via our MAUI UI and to then send the message through to the appropriate notification channel. We will add code to the constructor which simply routes messages from IMessenger like this

public MainActivity()
{
  var messenger = MauiApplication.Current.Services.GetService<IMessenger>();

  messenger.Register<MessageData>(this, (recipient, message) =>
  {
    if (message.Channel == 1)
    {
      SendOnChannel1(message.Title, message.Message);
    }
    else
    {
      SendOnChannel2(message.Title, message.Message);
    }
  });
}

We’re also going to need access to the NotificationManagerCompat so declare the following in the MainActivity class and override the OnCreate to set it, like this

private NotificationManagerCompat _notificationManager;

protected override void OnCreate(Bundle savedInstanceState)
{
  base.OnCreate(savedInstanceState);

  _notificationManager = NotificationManagerCompat.From(this);
}

Finally we’ll need the code for the SendOnChannel1 and SendOnChannel2 methods, which look like this

private void SendOnChannel1(string title, string message)
{
  var notification = new NotificationCompat.Builder(this, MainApplication.Channel1Id)
    .SetSmallIcon(Resource.Drawable.abc_ab_share_pack_mtrl_alpha)
    .SetContentTitle(title)
    .SetContentText(message)
    .SetPriority(NotificationCompat.PriorityHigh)
    .SetCategory(NotificationCompat.CategoryMessage)
    .Build();

    _notificationManager.Notify(1, notification);
}

private void SendOnChannel2(string title, string message)
{
  var notification = new NotificationCompat.Builder(this, MainApplication.Channel2Id)
    .SetSmallIcon(Resource.Drawable.abc_btn_check_material)
    .SetContentTitle(title)
    .SetContentText(message)
    .SetPriority(NotificationCompat.PriorityLow)
    .Build();

    _notificationManager.Notify(2, notification);
}

The two methods are currently pretty much the same, each Builder is supplied with a unique channel id (we’re reusing ours from the MainApplication). A notification requires a small icon, so we’re just reusing built in icons here (which may not show up very well) but feel free to add your own icon (which we will do in a subsequent post if you’re not sure how to). We’re setting priorities differently. If I recall these are duplicated from the code where we create the channels due to different versions of Android API support. So, we’re setting a category on channel 1 which just creates a category association with the channel – we’ll see more on this in a later post. Lastly we essentially add the notification to the NotificationManager with a unique id and we’re done.

Running our application

Now if you run your application, I’m using the Pixel 3 emulator and click the “Send on Channel 1” button, you should see a popup with the title you supplied (or defaults to Title in the text entry) and message you supplied (again defaulted to Message in the text entry) and an audible (annoying) sound, if you click it again you’ll get the same again. If, on the other hand you now click “Send on Channel 2” you’ll see another icon on the status bar appear for the channel 2 messages, but no popup or sound, this is because anything above low importance makes the sound (again unless disabled by the user). Low priority just displays the icon and you can view the message(s) by dragging down the status bar to view notifications.

So, that’s it for part 1. We now have the core MAUI code, we can pass messages around which are received by the platform specific code and which turns those messages from the UI into notifications for Android to display.

Code

Code for this an subsequent posts is found on my blog project.