Skip to content

Commit 0de8dee

Browse files
authored
Merge pull request #1 from jfversluis/improve
Add tests & bugfix resulting from it
2 parents 6ed841b + ca4c475 commit 0de8dee

14 files changed

+1249
-125
lines changed

.github/workflows/ci.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,9 @@ jobs:
1515

1616
steps:
1717
- uses: actions/checkout@v4
18+
19+
- name: Test
20+
run: dotnet test tests\Plugin.Maui.MessagingCenter.UnitTests.csproj
21+
1822
- name: Build
1923
run: dotnet build src\Plugin.Maui.MessagingCenter.sln -c Release

.github/workflows/release-nuget.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ jobs:
2121
run: |
2222
$version="${{github.ref_name}}".TrimStart("v")
2323
"version-without-v=$version" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
24+
- name: Test
25+
run: dotnet test tests\Plugin.Maui.MessagingCenter.UnitTests.csproj
2426
- name: Pack
2527
run: dotnet pack src\Plugin.Maui.MessagingCenter.sln -c Release -p:PackageVersion=${{ steps.get_version.outputs.version-without-v }}
2628
- name: Push

BEHAVIOR_DIFFERENCES.md

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# Behavior Differences from .NET MAUI MessagingCenter
2+
3+
This document outlines the key behavioral differences between this plugin's MessagingCenter implementation and the original .NET MAUI MessagingCenter.
4+
5+
## Multiple Subscriptions to Same Message
6+
7+
### .NET MAUI MessagingCenter Behavior
8+
- **Allows multiple subscriptions**: The same subscriber can subscribe to the same message type multiple times
9+
- **All callbacks invoked**: When a message is sent, all registered callbacks for that subscriber are invoked
10+
- **Manual management required**: Developers must be careful to avoid unintended duplicate subscriptions
11+
12+
Example (would work in .NET MAUI MessagingCenter):
13+
```csharp
14+
var subscriber = new object();
15+
MessagingCenter.Subscribe<MyClass, string>(subscriber, "test", (sender, args) => Console.WriteLine("Handler 1"));
16+
MessagingCenter.Subscribe<MyClass, string>(subscriber, "test", (sender, args) => Console.WriteLine("Handler 2"));
17+
MessagingCenter.Send(this, "test", "message"); // Both handlers would be called
18+
```
19+
20+
### This Plugin's Behavior
21+
- **Prevents duplicate subscriptions**: Throws `InvalidOperationException` when attempting to subscribe the same recipient to the same message type multiple times
22+
- **Stricter subscription management**: Forces developers to unsubscribe before subscribing again
23+
- **Based on WeakReferenceMessenger**: Inherits this behavior from the underlying `CommunityToolkit.Mvvm.Messaging.WeakReferenceMessenger`
24+
25+
Example (throws exception in this plugin):
26+
```csharp
27+
var subscriber = new object();
28+
MessagingCenter.Subscribe<MyClass, string>(subscriber, "test", (sender, args) => Console.WriteLine("Handler 1"));
29+
// This will throw InvalidOperationException: "The target recipient has already subscribed to the target message."
30+
MessagingCenter.Subscribe<MyClass, string>(subscriber, "test", (sender, args) => Console.WriteLine("Handler 2"));
31+
```
32+
33+
## Subscribe-Unsubscribe-Subscribe Pattern
34+
35+
### Both Implementations
36+
Both implementations correctly support the subscribe-unsubscribe-subscribe pattern:
37+
38+
```csharp
39+
var subscriber = new object();
40+
41+
// Subscribe
42+
MessagingCenter.Subscribe<MyClass>(subscriber, "test", sender => Console.WriteLine("Received"));
43+
44+
// Send (message received)
45+
MessagingCenter.Send(this, "test");
46+
47+
// Unsubscribe
48+
MessagingCenter.Unsubscribe<MyClass>(subscriber, "test");
49+
50+
// Send (message not received)
51+
MessagingCenter.Send(this, "test");
52+
53+
// Subscribe again
54+
MessagingCenter.Subscribe<MyClass>(subscriber, "test", sender => Console.WriteLine("Received again"));
55+
56+
// Send (message received)
57+
MessagingCenter.Send(this, "test");
58+
```
59+
60+
## Recommendations
61+
62+
### If you need multiple handlers for the same message
63+
Instead of multiple subscriptions, consider:
64+
65+
1. **Single subscription with multiple actions**:
66+
```csharp
67+
MessagingCenter.Subscribe<MyClass, string>(subscriber, "test", (sender, args) => {
68+
HandleFirst(args);
69+
HandleSecond(args);
70+
});
71+
```
72+
73+
2. **Different message names**:
74+
```csharp
75+
MessagingCenter.Subscribe<MyClass, string>(subscriber, "test1", FirstHandler);
76+
MessagingCenter.Subscribe<MyClass, string>(subscriber, "test2", SecondHandler);
77+
```
78+
79+
3. **Multiple subscribers**:
80+
```csharp
81+
MessagingCenter.Subscribe<MyClass, string>(subscriber1, "test", FirstHandler);
82+
MessagingCenter.Subscribe<MyClass, string>(subscriber2, "test", SecondHandler);
83+
```
84+
85+
### Benefits of stricter behavior
86+
- **Prevents accidental duplicate subscriptions** that could cause unexpected behavior
87+
- **Clearer subscription management** - you know exactly what's subscribed
88+
- **Reduced memory usage** - no accidental handler accumulation
89+
- **Earlier error detection** - duplicate subscription attempts are caught immediately
90+
91+
## Migration from .NET MAUI MessagingCenter
92+
93+
If you're migrating from .NET MAUI MessagingCenter and encounter `InvalidOperationException` for duplicate subscriptions:
94+
95+
1. **Review your subscription logic** to ensure you're not subscribing multiple times unintentionally
96+
2. **Add proper unsubscribe calls** before re-subscribing
97+
3. **Consider if multiple handlers are actually needed** or if the logic can be consolidated

README.md

Lines changed: 117 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,126 @@ Available on [NuGet](http://www.nuget.org/packages/Plugin.Maui.MessagingCenter).
1616

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

19+
## Getting Started
20+
21+
### 1. Add the Using Statement
22+
23+
After installation, add the using statement to your files:
24+
25+
```csharp
26+
using Plugin.Maui.MessagingCenter;
27+
```
28+
29+
Or add it globally in your `GlobalUsings.cs` file:
30+
31+
```csharp
32+
global using Plugin.Maui.MessagingCenter;
33+
```
34+
35+
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.
36+
37+
### 2. Basic Usage Examples
38+
39+
#### Sending Messages with Arguments
40+
41+
```csharp
42+
// Send a message with data
43+
MessagingCenter.Send<MainPage, string>(this, "LocationUpdate", "New York");
44+
45+
// In another class, subscribe to receive the message
46+
MessagingCenter.Subscribe<MainPage, string>(this, "LocationUpdate", (sender, location) =>
47+
{
48+
// Handle the location update
49+
DisplayAlert("Location", $"New location: {location}", "OK");
50+
});
51+
```
52+
53+
#### Sending Messages without Arguments
54+
55+
```csharp
56+
// Send a simple notification message
57+
MessagingCenter.Send<MainPage>(this, "RefreshData");
58+
59+
// Subscribe to the notification
60+
MessagingCenter.Subscribe<MainPage>(this, "RefreshData", (sender) =>
61+
{
62+
// Refresh your data
63+
LoadData();
64+
});
65+
```
66+
67+
#### Using with ViewModels
68+
69+
```csharp
70+
public class MainViewModel : INotifyPropertyChanged
71+
{
72+
public void NotifyDataChanged()
73+
{
74+
// Send message from ViewModel
75+
MessagingCenter.Send<MainViewModel, DataModel>(this, "DataUpdated", newData);
76+
}
77+
}
78+
79+
public partial class DetailPage : ContentPage
80+
{
81+
public DetailPage()
82+
{
83+
InitializeComponent();
84+
85+
// Subscribe to ViewModel messages
86+
MessagingCenter.Subscribe<MainViewModel, DataModel>(this, "DataUpdated", (sender, data) =>
87+
{
88+
// Update UI with new data
89+
UpdateDisplay(data);
90+
});
91+
}
92+
}
93+
```
94+
95+
### 3. Important: Unsubscribing
96+
97+
Always unsubscribe when your object is disposed to prevent memory leaks:
98+
99+
```csharp
100+
public partial class MyPage : ContentPage
101+
{
102+
protected override void OnDisappearing()
103+
{
104+
// Unsubscribe from all messages this page subscribed to
105+
MessagingCenter.Unsubscribe<MainViewModel, DataModel>(this, "DataUpdated");
106+
MessagingCenter.Unsubscribe<MainPage>(this, "RefreshData");
107+
108+
base.OnDisappearing();
109+
}
110+
}
111+
```
112+
113+
### 4. Sender Filtering
114+
115+
You can filter messages to only receive them from specific senders:
116+
117+
```csharp
118+
// Only receive messages from a specific instance
119+
var specificViewModel = new MainViewModel();
120+
MessagingCenter.Subscribe<MainViewModel, string>(this, "StatusUpdate",
121+
(sender, status) => {
122+
// Handle status update
123+
}, specificViewModel); // Only from this specific instance
124+
```
125+
19126
## API Usage
20127

21-
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.
128+
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).
129+
130+
## Important Behavioral Differences
131+
132+
⚠️ **This implementation has stricter subscription behavior than the original .NET MAUI MessagingCenter:**
133+
134+
- **Multiple subscriptions to the same message type by the same subscriber will throw an `InvalidOperationException`**
135+
- This prevents accidental duplicate subscriptions and potential memory leaks
136+
- If you need multiple handlers, consider using different message names or consolidating logic into a single handler
22137

23-
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).
138+
For detailed information about behavioral differences, see [BEHAVIOR_DIFFERENCES.md](BEHAVIOR_DIFFERENCES.md).
24139

25140
## Acknowledgements
26141

samples/Plugin.Maui.MessagingCenter.Sample.sln

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
Microsoft Visual Studio Solution File, Format Version 12.00
1+
Microsoft Visual Studio Solution File, Format Version 12.00
22
# Visual Studio Version 17
33
VisualStudioVersion = 17.0.31611.283
44
MinimumVisualStudioVersion = 10.0.40219.1
55
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Plugin.Maui.MessagingCenter.Sample", "Plugin.Maui.MessagingCenter.Sample\Plugin.Maui.MessagingCenter.Sample.csproj", "{490BB138-9606-4FFF-8AAD-841C5B1ED059}"
66
EndProject
77
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Plugin.Maui.MessagingCenter", "..\src\Plugin.Maui.MessagingCenter\Plugin.Maui.MessagingCenter.csproj", "{B60F3174-E978-4F2B-B117-F6F0327594C8}"
8-
8+
EndProject
9+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plugin.Maui.MessagingCenter.UnitTests", "..\tests\Plugin.Maui.MessagingCenter.UnitTests.csproj", "{958E64EA-FEC8-4814-93F5-4C8BF5F42BDF}"
910
EndProject
1011
Global
1112
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -23,6 +24,10 @@ Global
2324
{B60F3174-E978-4F2B-B117-F6F0327594C8}.Debug|Any CPU.Build.0 = Debug|Any CPU
2425
{B60F3174-E978-4F2B-B117-F6F0327594C8}.Release|Any CPU.ActiveCfg = Release|Any CPU
2526
{B60F3174-E978-4F2B-B117-F6F0327594C8}.Release|Any CPU.Build.0 = Release|Any CPU
27+
{958E64EA-FEC8-4814-93F5-4C8BF5F42BDF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
28+
{958E64EA-FEC8-4814-93F5-4C8BF5F42BDF}.Debug|Any CPU.Build.0 = Debug|Any CPU
29+
{958E64EA-FEC8-4814-93F5-4C8BF5F42BDF}.Release|Any CPU.ActiveCfg = Release|Any CPU
30+
{958E64EA-FEC8-4814-93F5-4C8BF5F42BDF}.Release|Any CPU.Build.0 = Release|Any CPU
2631
EndGlobalSection
2732
GlobalSection(SolutionProperties) = preSolution
2833
HideSolutionNode = FALSE
Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
1-
namespace Plugin.Maui.MessagingCenter.Sample;
1+

2+
namespace Plugin.Maui.MessagingCenter.Sample;
23

34
public partial class App : Application
45
{
56
public App()
67
{
78
InitializeComponent();
8-
9-
MainPage = new AppShell();
109
}
10+
11+
protected override Window CreateWindow(IActivationState activationState)
12+
{
13+
return new Window(new AppShell());
14+
}
1115
}

samples/Plugin.Maui.MessagingCenter.Sample/Plugin.Maui.MessagingCenter.Sample.csproj

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

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

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

2120
<!-- Versions -->
2221
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
2322
<ApplicationVersion>1</ApplicationVersion>
2423

2524
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">14.2</SupportedOSPlatformVersion>
26-
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">14.0</SupportedOSPlatformVersion>
25+
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">15.0</SupportedOSPlatformVersion>
2726
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">21.0</SupportedOSPlatformVersion>
2827
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</SupportedOSPlatformVersion>
2928
<TargetPlatformMinVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</TargetPlatformMinVersion>

src/Plugin.Maui.MessagingCenter/GenericMessage.cs

Lines changed: 0 additions & 12 deletions
This file was deleted.

0 commit comments

Comments
 (0)