MvvmCross: Best way to raise event in ViewModel?
Asked Answered
A

2

6

I currently have a View (Android Fragment) and a coresponding ViewModel. I now want to raise an event in the ViewModel which the View can subscribe to.

What's the best way to archive this? I heard a regular C# event (delegate) can lead to memory leaks? Is this the reason for the WeakSubscribe function? How to bind it to an event?

Apogeotropism answered 4/9, 2016 at 9:18 Comment(6)
What is this event doing when it is handled? Would a property change notification work?Concretion
The ViewModel manages a list of items (which are Sub-ViewModels), As soon as the user clicks on one of the subitems, the ViewModel should notify the view that the subitem was clicked. I also need the information which item was clicked.Apogeotropism
What are you doing with this information in the view?Concretion
I am showing a Toast to inform the user that the item was added successfully to a favorite list.Apogeotropism
I personally wouldn't add an event and handle it in the view - instead I would create a notification service exposed via interface and call this in the view model, and have an implementation in the android code that shows a toast. That way it's more unit testable, and easier to make cross platform - just write an iOS implementation of the service that shows the notification in whatever way you want.Concretion
I agree that this way is suitable for many problems. But this way won't work if you wan't to show a Snackbar, which needs an View as parameter or want to raise some other action in your view (like change a color of a text for example). In this case a "Notification Service" would be too limited or not? For now I stick with an Toast but I plan to replace it with a Snackbar very soon.Apogeotropism
U
12

To prevent memory leaks in your views, every event you subscribed to needs to be unsubscribed from, either way using WeakSubscribe or subscribing to events as usual.

A common scenario would be subscribing on:

  • Android OnResume()
  • iOS ViewWillAppear()

then dispose the subscription on:

  • Android OnPause()
  • iOS ViewWillDisappear()

WeakSubscribe

If you want to "listen" for ViewModel property changes, WeakSubscribe comes in handy:

private IDisposable _selectedItemToken;

_selectedItemToken = ViewModel.WeakSubscribe(() => 
    ViewModel.SelectedItem, (sender, eventArgs) => {
        // do something
});

Just notice that WeakSubscribe() returns an MvxWeakEventSubscription that is also IDisposable. You need to save a reference to that subscription in your view and dispose it when thew view is no longer needed. There are two reasons to keep that reference:

  1. You can dispose it later
  2. If you don´t keep it, your lambda event handler may not always work

Later on...

_selectedItemToken?.Dispose();

Normal event subscription

If you just need to subscribe to another kind of event (not property changes) in your ViewModel, you don´t actually need WeakSubscribe. You can just add an event listener to the ViewModel, as you would do with any object.

ViewModel.AnEvent += YourDelegate;

Later on...

ViewModel.AnEvent -= YourDelegate;

Don´t forget the last step. That will prevent memory leaks. As I said, Android OnPause() and iOS ViewWillDisappear() are good places to do it.

That way your ViewModel won´t be stuck in memory when the view is disposed thus your view can be garbage collected correctly.

Ursula answered 4/9, 2016 at 15:7 Comment(7)
That looks promising. Would you mind if I ask you for a simple example on how to subscribe to an event with an custom delegate type? I run into all sorts of troubles with this.Apogeotropism
What did you try already? what kind of troubles are running into?Ursula
I tried it with the following snippet: _subscription = typeof(TViewModel) .GetEvent("ItemAdded") .WeakSubscribe(this.ViewModel, OnItemAdded); but I get an error that says "Cannot convert instance argument type "System.Reflection.EventInfo" [mscorlib, Version=2.0.5.0] to "System.Reflection.EventInfo" [mscorlib, Version=4.0.0.0]. That's propably unrelated but I am not completely sure.Apogeotropism
I added a sample to the answerUrsula
I get it now. I totally misunderstood your question. I will update it accordinglyUrsula
Thank you. Just a last question to clarify things: SelectedItem would be a regular public property and not an event? So events aren't supported I always have to go the way over a property and the NotifyPropertChanged mechanism?Apogeotropism
Fantastic! Thank you.Apogeotropism
P
1

You can create a leak if you subscribe to an event in a temporary object and don't unsubscribe before releasing the temporary object. Little chance for that in your case as the view model most likely will be created only once.

Since you are using mvvm, an alternative to events are Messengers which you can find implemented in popular mvvm-frameworks like MvvmLight or MvvmCross. This gives you truly decoupled events as you only need to know the format of the message and don't need to know anything about the sender (which in your case is the ViewModel). With messenger you only subscribe to a message-type and the sender can be anywhere in the app.

Programme answered 4/9, 2016 at 11:36 Comment(1)
There´s no need to decouple the view from the ViewModel. That´s true for the opposite (ViewModel shouldn´t know about the View). Why using a messenger when you can subscribe directly to ViewModel changes?Ursula

© 2022 - 2024 — McMap. All rights reserved.