You've probably hit a point where you needed to use Xamarin.Forms MessagingCenter
. It's an easy tool to use in the framework, however, it can cause a lot of headaches if you forget to unsubscribe from messages. If you are not familiar with MessagingCenter
, take a look at the documentation here.
Navigation Messages
I have commonly seen MessagingCenter
used to send a message that triggers navigation within the app. For example:
// Send a message to trigger navigation from some place
MessagingCenter.Send<string>(this, "NavigateDetail", detailItem);
// Some other place receives it and does the navigation
MessagingCenter.Subscribe<string, DetailClass>(this, "NavigateDetail", async (detail) =>
{
await Naviation.PopAsync();
});
This might work the first time, but lets assume you subscribed in the constructor of your Page
class. The next time you get this "NavigateDetail" message, you might run into a crash:
System.ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection.
...
at System.ThrowHelper.ThrowArgumentOutOfRangeException (ExceptionArgument argument, ExceptionResource resource) [0x00000] in <snipped>/throwhelper.cs:93
at System.ThrowHelper.ThrowArgumentOutOfRangeException () [0x00000] in <snipped>/throwhelper.cs:56
at System.Collections.Generic.List`1[T].get_Item (Int32 index) [0x0000c] in <snipped>/list.cs:181
at Xamarin.Forms.NavigationProxy.Pop () [0x00012] in <filename unknown>:0
at Xamarin.Forms.NavigationProxy.OnPopAsync (Boolean animated) [0x00012] in <filename unknown>:0
at Xamarin.Forms.NavigationProxy.PopAsync () [0x00000] in <filename unknown>:0
Problem: A message is subscribed to whenever the Page
is created and it's never unsubscribed from.
Solution: Unsubscribe from the message in the message action/handler, or use a pattern of subscribing in OnAppearing
and unsubscribing in OnDisappearing
.
You could solve this by doing something like this:
// Some other place receives it and does the navigation
MessagingCenter.Subscribe<string, DetailClass>(this, "NavigateDetail", async (detail) =>
{
MessagingCenter.Unsubscribe<string>(this, "NavigateDetail");
await Naviation.PopAsync();
});
Without this, the Page
that is being popped (PopAsync
) would retain in memory and still receive the message. However, it's not part of the Navigation hierarchy anymore, so Navigation.PopAsync()
throws an exception. It's even easier to see the issues caused by this by logging the object:
MessagingCenter.Subscribe<string, DetailClass>(this, "NavigateDetail", async (detail) =>
{
System.Diagnostics.Debug.WriteLine("NavigateDetail: " + this.GetHashCode());
await Naviation.PopAsync();
});
You'll see multiple objects logging this message, which is not what you would want to happen.
Messages in Renderers
Be careful when using MessagingCenter
in renderers. When a Page
is removed (PopAsync
, PopModalAsync
) from Navigation, the renderers are disposed of. You can override Dispose
and unsubscribe from your messages there, same as above.
Troubleshooting
If you still have issues, even after unsubscribing, make sure that your <T>
arguments for Unsubscribe
match your message. For example, given this subscription:
MessagingCenter.Subscribe<byte[]>(this, "ImageData", (data) =>
{
MessagingCenter.Unsubscribe<string>(this, "ImageData");
// Do work
});
This will compile and run fine, except the message is not properly unsubscribed from because the <T>
arguments don't match. The message name is the same, but both things must match. I've missed that a few times.
This weeks coffee, Alabaster's Nicaragua / Yader / Direct Trade
Mango & floral aromatics, yellow delicious apple, crisp & well balanced, pomegranate finish.
Comments