Skip to content

Add tests & bugfix resulting from it #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
May 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,9 @@ jobs:

steps:
- uses: actions/checkout@v4

- name: Test
run: dotnet test tests\Plugin.Maui.MessagingCenter.UnitTests.csproj

- name: Build
run: dotnet build src\Plugin.Maui.MessagingCenter.sln -c Release
2 changes: 2 additions & 0 deletions .github/workflows/release-nuget.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ jobs:
run: |
$version="${{github.ref_name}}".TrimStart("v")
"version-without-v=$version" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
- name: Test
run: dotnet test tests\Plugin.Maui.MessagingCenter.UnitTests.csproj
- name: Pack
run: dotnet pack src\Plugin.Maui.MessagingCenter.sln -c Release -p:PackageVersion=${{ steps.get_version.outputs.version-without-v }}
- name: Push
Expand Down
97 changes: 97 additions & 0 deletions BEHAVIOR_DIFFERENCES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# Behavior Differences from .NET MAUI MessagingCenter

This document outlines the key behavioral differences between this plugin's MessagingCenter implementation and the original .NET MAUI MessagingCenter.

## Multiple Subscriptions to Same Message

### .NET MAUI MessagingCenter Behavior
- **Allows multiple subscriptions**: The same subscriber can subscribe to the same message type multiple times
- **All callbacks invoked**: When a message is sent, all registered callbacks for that subscriber are invoked
- **Manual management required**: Developers must be careful to avoid unintended duplicate subscriptions

Example (would work in .NET MAUI MessagingCenter):
```csharp
var subscriber = new object();
MessagingCenter.Subscribe<MyClass, string>(subscriber, "test", (sender, args) => Console.WriteLine("Handler 1"));
MessagingCenter.Subscribe<MyClass, string>(subscriber, "test", (sender, args) => Console.WriteLine("Handler 2"));
MessagingCenter.Send(this, "test", "message"); // Both handlers would be called
```

### This Plugin's Behavior
- **Prevents duplicate subscriptions**: Throws `InvalidOperationException` when attempting to subscribe the same recipient to the same message type multiple times
- **Stricter subscription management**: Forces developers to unsubscribe before subscribing again
- **Based on WeakReferenceMessenger**: Inherits this behavior from the underlying `CommunityToolkit.Mvvm.Messaging.WeakReferenceMessenger`

Example (throws exception in this plugin):
```csharp
var subscriber = new object();
MessagingCenter.Subscribe<MyClass, string>(subscriber, "test", (sender, args) => Console.WriteLine("Handler 1"));
// This will throw InvalidOperationException: "The target recipient has already subscribed to the target message."
MessagingCenter.Subscribe<MyClass, string>(subscriber, "test", (sender, args) => Console.WriteLine("Handler 2"));
```

## Subscribe-Unsubscribe-Subscribe Pattern

### Both Implementations
Both implementations correctly support the subscribe-unsubscribe-subscribe pattern:

```csharp
var subscriber = new object();

// Subscribe
MessagingCenter.Subscribe<MyClass>(subscriber, "test", sender => Console.WriteLine("Received"));

// Send (message received)
MessagingCenter.Send(this, "test");

// Unsubscribe
MessagingCenter.Unsubscribe<MyClass>(subscriber, "test");

// Send (message not received)
MessagingCenter.Send(this, "test");

// Subscribe again
MessagingCenter.Subscribe<MyClass>(subscriber, "test", sender => Console.WriteLine("Received again"));

// Send (message received)
MessagingCenter.Send(this, "test");
```

## Recommendations

### If you need multiple handlers for the same message
Instead of multiple subscriptions, consider:

1. **Single subscription with multiple actions**:
```csharp
MessagingCenter.Subscribe<MyClass, string>(subscriber, "test", (sender, args) => {
HandleFirst(args);
HandleSecond(args);
});
```

2. **Different message names**:
```csharp
MessagingCenter.Subscribe<MyClass, string>(subscriber, "test1", FirstHandler);
MessagingCenter.Subscribe<MyClass, string>(subscriber, "test2", SecondHandler);
```

3. **Multiple subscribers**:
```csharp
MessagingCenter.Subscribe<MyClass, string>(subscriber1, "test", FirstHandler);
MessagingCenter.Subscribe<MyClass, string>(subscriber2, "test", SecondHandler);
```

### Benefits of stricter behavior
- **Prevents accidental duplicate subscriptions** that could cause unexpected behavior
- **Clearer subscription management** - you know exactly what's subscribed
- **Reduced memory usage** - no accidental handler accumulation
- **Earlier error detection** - duplicate subscription attempts are caught immediately

## Migration from .NET MAUI MessagingCenter

If you're migrating from .NET MAUI MessagingCenter and encounter `InvalidOperationException` for duplicate subscriptions:

1. **Review your subscription logic** to ensure you're not subscribing multiple times unintentionally
2. **Add proper unsubscribe calls** before re-subscribing
3. **Consider if multiple handlers are actually needed** or if the logic can be consolidated
119 changes: 117 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,126 @@ Available on [NuGet](http://www.nuget.org/packages/Plugin.Maui.MessagingCenter).

Install with the dotnet CLI: `dotnet add package Plugin.Maui.MessagingCenter`, or through the NuGet Package Manager in Visual Studio.

## Getting Started

### 1. Add the Using Statement

After installation, add the using statement to your files:

```csharp
using Plugin.Maui.MessagingCenter;
```

Or add it globally in your `GlobalUsings.cs` file:

```csharp
global using Plugin.Maui.MessagingCenter;
```

That's it! You can now use the `MessagingCenter` class just like you did in .NET MAUI. Since the API is compatible with the .NET MAUI MessagingCenter, you can use it in the same way as before, there should be no need to change your existing code.

### 2. Basic Usage Examples

#### Sending Messages with Arguments

```csharp
// Send a message with data
MessagingCenter.Send<MainPage, string>(this, "LocationUpdate", "New York");

// In another class, subscribe to receive the message
MessagingCenter.Subscribe<MainPage, string>(this, "LocationUpdate", (sender, location) =>
{
// Handle the location update
DisplayAlert("Location", $"New location: {location}", "OK");
});
```

#### Sending Messages without Arguments

```csharp
// Send a simple notification message
MessagingCenter.Send<MainPage>(this, "RefreshData");

// Subscribe to the notification
MessagingCenter.Subscribe<MainPage>(this, "RefreshData", (sender) =>
{
// Refresh your data
LoadData();
});
```

#### Using with ViewModels

```csharp
public class MainViewModel : INotifyPropertyChanged
{
public void NotifyDataChanged()
{
// Send message from ViewModel
MessagingCenter.Send<MainViewModel, DataModel>(this, "DataUpdated", newData);
}
}

public partial class DetailPage : ContentPage
{
public DetailPage()
{
InitializeComponent();

// Subscribe to ViewModel messages
MessagingCenter.Subscribe<MainViewModel, DataModel>(this, "DataUpdated", (sender, data) =>
{
// Update UI with new data
UpdateDisplay(data);
});
}
}
```

### 3. Important: Unsubscribing

Always unsubscribe when your object is disposed to prevent memory leaks:

```csharp
public partial class MyPage : ContentPage
{
protected override void OnDisappearing()
{
// Unsubscribe from all messages this page subscribed to
MessagingCenter.Unsubscribe<MainViewModel, DataModel>(this, "DataUpdated");
MessagingCenter.Unsubscribe<MainPage>(this, "RefreshData");

base.OnDisappearing();
}
}
```

### 4. Sender Filtering

You can filter messages to only receive them from specific senders:

```csharp
// Only receive messages from a specific instance
var specificViewModel = new MainViewModel();
MessagingCenter.Subscribe<MainViewModel, string>(this, "StatusUpdate",
(sender, status) => {
// Handle status update
}, specificViewModel); // Only from this specific instance
```

## API Usage

After you have installed this library, you can either add the using statement at the top of each file where you are using MessagingCenter (`using Plugin.Maui.MessagingCenter;`), or opt for a [global using](https://learn.microsoft.com/dotnet/csharp/language-reference/keywords/using-directive#global-modifier) statement so you only need to do it once.
The API is compatible with the .NET MAUI MessagingCenter APIs. For more detailed documentation, see the .NET MAUI MessagingCenter [reference](https://learn.microsoft.com/dotnet/maui/fundamentals/messagingcenter) and the MVVM Toolkit Messenger [documentation](https://learn.microsoft.com/dotnet/communitytoolkit/mvvm/messenger).

## Important Behavioral Differences

⚠️ **This implementation has stricter subscription behavior than the original .NET MAUI MessagingCenter:**

- **Multiple subscriptions to the same message type by the same subscriber will throw an `InvalidOperationException`**
- This prevents accidental duplicate subscriptions and potential memory leaks
- If you need multiple handlers, consider using different message names or consolidating logic into a single handler

The API can be used the same as the .NET MAUI MessagingCenter APIs. You probably came here because you are already using the MessagingCenter in your .NET MAUI app, so you probably don't need more explanation. If you do need a reference, please find the documentation on the .NET MAUI MessagingCenter [here](https://learn.microsoft.com/dotnet/maui/fundamentals/messagingcenter) and the MVVM Toolkit Messenger [here](https://learn.microsoft.com/dotnet/communitytoolkit/mvvm/messenger).
For detailed information about behavioral differences, see [BEHAVIOR_DIFFERENCES.md](BEHAVIOR_DIFFERENCES.md).

## Acknowledgements

Expand Down
9 changes: 7 additions & 2 deletions samples/Plugin.Maui.MessagingCenter.Sample.sln
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
Microsoft Visual Studio Solution File, Format Version 12.00
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31611.283
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Plugin.Maui.MessagingCenter.Sample", "Plugin.Maui.MessagingCenter.Sample\Plugin.Maui.MessagingCenter.Sample.csproj", "{490BB138-9606-4FFF-8AAD-841C5B1ED059}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Plugin.Maui.MessagingCenter", "..\src\Plugin.Maui.MessagingCenter\Plugin.Maui.MessagingCenter.csproj", "{B60F3174-E978-4F2B-B117-F6F0327594C8}"

EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plugin.Maui.MessagingCenter.UnitTests", "..\tests\Plugin.Maui.MessagingCenter.UnitTests.csproj", "{958E64EA-FEC8-4814-93F5-4C8BF5F42BDF}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand All @@ -23,6 +24,10 @@ Global
{B60F3174-E978-4F2B-B117-F6F0327594C8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B60F3174-E978-4F2B-B117-F6F0327594C8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B60F3174-E978-4F2B-B117-F6F0327594C8}.Release|Any CPU.Build.0 = Release|Any CPU
{958E64EA-FEC8-4814-93F5-4C8BF5F42BDF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{958E64EA-FEC8-4814-93F5-4C8BF5F42BDF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{958E64EA-FEC8-4814-93F5-4C8BF5F42BDF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{958E64EA-FEC8-4814-93F5-4C8BF5F42BDF}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
10 changes: 7 additions & 3 deletions samples/Plugin.Maui.MessagingCenter.Sample/App.xaml.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
namespace Plugin.Maui.MessagingCenter.Sample;

namespace Plugin.Maui.MessagingCenter.Sample;

public partial class App : Application
{
public App()
{
InitializeComponent();

MainPage = new AppShell();
}

protected override Window CreateWindow(IActivationState activationState)
{
return new Window(new AppShell());
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net8.0-android;net8.0-ios;net8.0-maccatalyst</TargetFrameworks>
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net8.0-windows10.0.19041.0</TargetFrameworks>
<TargetFrameworks>net9.0-android;net9.0-ios;net9.0-maccatalyst</TargetFrameworks>
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net9.0-windows10.0.19041.0</TargetFrameworks>
<!-- Uncomment to also build the tizen app. You will need to install tizen by following this: https://github.com/Samsung/Tizen.NET -->
<!-- <TargetFrameworks>$(TargetFrameworks);net8.0-tizen</TargetFrameworks> -->
<OutputType>Exe</OutputType>
Expand All @@ -16,14 +16,13 @@

<!-- App Identifier -->
<ApplicationId>com.companyname.pluginsample</ApplicationId>
<ApplicationIdGuid>A20E30BB-3BF7-4ACB-89F2-596834136909</ApplicationIdGuid>

<!-- Versions -->
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
<ApplicationVersion>1</ApplicationVersion>

<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">14.2</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">14.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">15.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">21.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</SupportedOSPlatformVersion>
<TargetPlatformMinVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</TargetPlatformMinVersion>
Expand Down
12 changes: 0 additions & 12 deletions src/Plugin.Maui.MessagingCenter/GenericMessage.cs

This file was deleted.

Loading