Before .NET MAUI: broadcast messages in Xamarin.Forms
Broadcast messages are the implementation of the so-called Publisher/Subscriber pattern. This allows an object (the publisher) to notify other objects (subscribers) that something happened in the logic of your app by sending a message. The publisher should never know who the subscribers are. This pattern is very common, especially with the Model-View-ViewModel, where viewmodels cannot open views directly, therefore they send messages that other views will catch and will behave accordingly (an example will come shortly).
In Xamarin.Forms, you could leverage the MessagingCenter
class. A message could be sent this way:
MessagingCenter.Send(this, "NotificationMessage");
Where this is the sender and NotificationMessage is the message identifier. Subscribers would register for messages as follows:
MessagingCenter.Subscribe<SenderObject>(this, "NotificationMessage", ((sender, e)=> { // Take action here });
In this case, the generic type parameter SenderObject is the type that sends the message. The this keyword represents the instance that is subscribing for the message, NotificationMessage is the message the code is subscribing for, and the last parameter is an action that is taken when the message is intercepted.
When the workflow ends, you should manually unsubscribe from messages as follows:
MessagingCenter.Unsubscribe<SenderObject>(this, "NotificationMessage");
This technique works in Xamarin.Forms and in .NET MAUI targeting .NET 6. However, in .NET 7, the MessagingCenter
has been deprecated and you need to use a different approach.
Broadcast messages in .NET MAUI with .NET 7
When a .NET MAUI project targets .NET 7, Microsoft recommends using the WeakReferenceMessenger
class from the CommunityToolkit.Mvvm library. This is a Nuget package by Microsoft that is intended to support the implementation of the Model-View-Viewmodel pattern with any .NET project, including .NET MAUI. So, create a new .NET MAUI project in Visual Studio 2022 and install the CommunityToolkit.Mvvm library from Nuget as follows:
The goal of the sample code is to open a secondary page from the MainPage
class via commanding. This requires the bound viewmodel to send a message when the command is invoked, and the caller page will open the secondary page when the message is intercepted. This might seem tricky, but it is actually how the Model-View-ViewModel works: you should not handle buttons' Clicked events, instead, you should bind buttons to commands in the viewmodel. However, the viewmodel cannot open another view (at least for the MVVM principles) so it sends a message and a view will perform actions against other views.
Messages at a glance
In .NET MAUI, messages are represented by a class that derives from ValueChangedMessage<T>
, where T is the data type that you want to exchange with subscribers. A message is then sent via the Send
method of the WeakReferenceMessenger
class. This is a singleton class, and its instance is exposed by the Default
property. Subscribers register for messages via the Register
method, which allows for taking actions when a message is intercepted. When subscribers are done, they should explicitly unregister from messages, which is accomplished via the Unregister
method. This is a very short summary, but in the next paragraphs, you will see how the implementation actually works in detail.
Implementing a message
Differently from Xamarin.Forms and the MessagingCenter
, in .NET 7 a message is not a string. Instead, it is a .NET type represented by the ValueChangedMessage<T>
class. An example will help to understand, so add a new class file called OpenWindowMessage
to the project and add the following code:
using CommunityToolkit.Mvvm.Messaging.Messages;
namespace adsMauiPublisherSubscriber {
public class OpenWindowMessage: ValueChangedMessage < bool > {
public OpenWindowMessage(bool value): base(value) {}
}
}
The new message class inherits from ValueChangedMessage
. You decide the data type that you want to handle. In this case, it is bool
, because this is enough to understand if a page has been opened (true) or not (false). However, you can definitely pass complex types depending on how structured is the information you want to send from the message class to subscribers. It is necessary to implement an empty constructor that invokes its base implementation.
Defining a ViewModel
The next step is implementing a ViewModel where you will also see the message in action. Add a new class called ViewModel.cs to the project, with the following code:
using CommunityToolkit.Mvvm.Messaging;
namespace adsMauiPublisherSubscriber {
public class ViewModel {
public Command OpenWindowCommand {
get {
return new Command(() => {
WeakReferenceMessenger.Default.Send(new OpenWindowMessage(true));
});
}
}
}
}
This is a very simple class with only one command, which is okay for instructional purposes. The OpenWindowCommand
will be later bound to a button in the UI. When invoked, it sends a broadcast message. This is accomplished by invoking the Send
method over a single instance of the WeakReferenceMessenger
class, represented by the Default
property. The method takes a message as an argument, and the constructor needs you to supply the value that you want to be sent to subscribers along with the message. Here the code is sending true
, so that callers understand that a new page must be opened. Again, remember that you can implement and send instances of more complex types depending on the structure of your information.
Adding a secondary page
Because we want to demonstrate how to open a new page from the main one via messages, it is necessary adding a new ContentPage to the project. Right-click the project name and select Add, New Item. In the Add New Item dialog select the .NET MAUI node on the left and then the .NET MAUI ContentPage (XAML) template. You can leave the default name unchanged, this is not relevant now. The only work needed on this page is sending another message when the page is closing. This can be accomplished by handling the OnDisappearing
method as follows:
protected override void OnDisappearing() {
base.OnDisappearing();
WeakReferenceMessenger.Default.Send(new OpenWindowMessage(false));
}
The code sends the same message, but this time the value is false
. This will make callers understand what happened on this side.
Registering for broadcast messages
Open the MainPage.xaml file and change the content for the Button object as follows:
<Button
x:Name="OpenWindowBtn"
Text="Click me" Command="{Binding OpenWindowCommand}"
HorizontalOptions="Center" />
This binds the button to a command in the ViewModel. In the code-behind file, replace the full code with the following (comments follow below):
using CommunityToolkit.Mvvm.Messaging;
namespace adsMauiPublisherSubscriber;
public partial class MainPage: ContentPage {
private ViewModel WindowViewModel {
get;
set;
}
public MainPage() {
InitializeComponent();
WindowViewModel = new ViewModel();
WeakReferenceMessenger.Default.Register < OpenWindowMessage > (this, HandleOpenWindowMessage);
BindingContext = WindowViewModel;
}
private async void HandleOpenWindowMessage(object recipient, OpenWindowMessage message) {
switch (message.Value) {
case true:
await Navigation.PushAsync(new NewPage1());
break;
default:
await DisplayAlert("Alert", "Secondary window was closed", "OK");
break;
}
}
}
In the constructor of the page, a new instance of the ViewModel is created and assigned to the page as the data source (BindingContext
). The page is also subscribing for messages of type OpenWindowMessage
, without knowing who the sender is. Registering to a message is accomplished via the Register
method of the WeakRefernceMessenger
class, which takes the message type as the generic type parameter, plus the instance of the subscriber (this
) and a method that contains the action to take when the message is intercepted. In this case, the action is handled by the HandleOpenWindowMessage
method, which is responsible for opening the secondary page or showing an alert when the secondary page is closed, depending on the value of the message.
Unsubscribing from messages
In the real-world development, you should also unsubscribe from messages when no longer needed. This can be accomplished by invoking the Unregister method as follows:
WeakReferenceMessenger.Default.Unregister<OpenWindowMessage>(this);
The Unregister method takes the message type as the generic parameter and the instance of the subscriber object. In the sample code, it is not necessary to do this because messages are subscribed in the main page, so closing the main page will also close the app and certainly unsubscribe from messages. But if you are handling messages on a secondary page, you should unsubscribe explicitly when no longer necessary.
Running the sample code
The following screenshots are taken from an Android simulator, but the result should be similar if you work with other devices as the project is cross-platform. If you run the sample app, you can see how the secondary page is opened when you click the button on the Main Page, but it is happening via command bindings and messages (not button clicks),
When this page is closed, the message is received and handled with a different value, so an alert is displayed,
Conclusions
As a developer working with .NET MAUI, it is very important that you know how to implement and handle broadcast messages especially if you target .NET 7 and if you need to work with the MVVM pattern. This article has explained how to do this in a simplified yet effective way.