I have achieved desired result with MessagingCenter, but I have got an information from reading Xamarin articles that MessagingCenter is not the preferred way to trigger 30 events. Additional to that I have to unsubscribe from MessagingCenter after action has been done. I want to have Settings page where I would have 30 settings that have to be changed across whole application in different views. How I can inject SettingsViewModel into other ViewModels in Xamarin.Forms application?
SettingsViewModel.cs:
namespace MessagingCenterApp.ViewModels
{
public class SettingsViewModel : BaseViewModel, ISettingsViewModel
{
public ICommand ChangeCommand { get; set; }
public SettingsViewModel()
{
Title = "Settings";
this.BoxColor = Color.Red;
this.ChangeCommand = new Command(this.ChangeColor);
}
private void ChangeColor()
{
this.BoxColor = Color.FromHex(this.BoxColorS);
MessagingCenter.Send<Object, Color>(this, "boxColor", this.BoxColor);
}
private Color _boxColor;
public Color BoxColor
{
get => _boxColor;
set
{
_boxColor = value;
this.OnPropertyChanged();
}
}
private string _boxColorS;
public string BoxColorS
{
get => Preferences.Get("BoxColor", "#17805d");
set
{
Preferences.Set("BoxColor", value);
this.ChangeColor();
this.OnSettingsChanged();
this.OnPropertyChanged();
}
}
public event EventHandler<SettingsChangedEventArgs> SettingsChanged;
private void OnSettingsChanged() => this.SettingsChanged?.Invoke(this, new SettingsChangedEventArgs(this.Settings));
public Settings Settings { get; private set; }
}
}
HomeViewModel.cs:
namespace MessagingCenterApp.ViewModels
{
public class HomeViewModel : BaseViewModel
{
public HomeViewModel()
{
this.Title = "Home";
MessagingCenter.Subscribe<Object, Color>(this, "boxColor", (sender, arg) =>
{
System.Diagnostics.Debug.WriteLine("received color = " arg);
this.BoxColor = arg;
});
this.BoxColor = Color.Red;
this.SettingsViewModel = new SettingsViewModel();
this.SettingsViewModel.SettingsChanged = OnSettingsChanged;
}
private void OnSettingsChanged(object sender, SettingsChangedEventArgs e)
{
throw new NotImplementedException();
}
private Color _boxColor;
public Color BoxColor
{
get => _boxColor;
set
{
_boxColor = value;
OnPropertyChanged();
}
}
private ISettingsViewModel SettingsViewModel { get; }
}
}
Should I somehow do all in MainViewModel? I mean:
namespace MessagingCenterApp.ViewModels
{
public class MainViewModel : BaseViewModel
{
public MainViewModel()
{
this.SettingsViewModel = new SettingsViewModel();
this.HomeViewModel = new HomeViewModel(this.SettingsViewModel);
}
public SettingsViewModel SettingsViewModel { get; set; }
public HomeViewModel HomeViewModel { get; }
}
}
Then initialized it in AppShell? I could not get this approach working.
Important! I don't want to use any MVVM framework! Only native behaviour.
CodePudding user response:
mvvmcross' Messenger is alleged to be "lighter weight" than X-Form's built-in Messaging Center.
I use mvvmcross Messenger by defining some helper methods in a "BasePage". Then each page inherits from "BasePage" rather than "ContentPage".
This automatically handles "unsubscribe" of each method. And makes it easier to manage mvvmcross' "subscription tokens".
BasePage.xaml.cs:
// If not using mvvmcross, this could inherit from ContentPage instead.
public class BasePage : MvxContentPage
{
protected readonly IMvxMessenger Messenger;
public BasePage()
{
this.Messenger = Mvx.IoCProvider.Resolve<IMvxMessenger>();
}
protected override void OnAppearing()
{
base.OnAppearing();
// Examples of subscribing to messages. Your subclasses of BasePage can also do this.
this.Subscribe<MyMessage1>(OnMyMessage1);
this.SubscribeOnMainThread<MyMessage2>(OnMyMessage2);
}
protected override void OnDisappearing()
{
UnsubscribeAll();
base.OnDisappearing();
}
#region Messenger Subscriptions
protected List<MvxSubscriptionToken> _subscriptions = new List<MvxSubscriptionToken>();
/// <summary>
/// Create subscription and add to "_subscriptions".
/// Call this from subclass' OnAppearing, once per subscription.
/// Automatically unsubscribed in OnDisappearing.
/// </summary>
/// <param name="token"></param>
/// <param name="msgType"></param>
protected void Subscribe<T>(Action<T> onMessage) where T : MvxMessage
{
var token = this.Messenger.Subscribe<T>(onMessage);
// Hold token to avoid GC of the subscription.
_subscriptions.Add(token);
}
protected void SubscribeOnMainThread<T>(Action<T> onMessage) where T : MvxMessage
{
var token = this.Messenger.SubscribeOnMainThread<T>(onMessage);
// Hold token to avoid GC of the subscription.
_subscriptions.Add(token);
}
/// <summary>
/// OnDisappearing calls this.
/// </summary>
private void UnsubscribeAll()
{
if (_subscriptions.Count > 0)
{
foreach (MvxSubscriptionToken token in _subscriptions)
{
// Per "https://www.mvvmcross.com/documentation/plugins/messenger", this is sufficient to Unsubscribe:
// "Subscriptions can be cancelled at any time using the Unsubscribe method on the IMvxMessenger or by calling Dispose() on the subscription token."
token.Dispose();
}
_subscriptions.Clear();
}
}
#endregion
}
For view models, class would be "BaseViewModel", that your view models inherit from. Contents similar to above, but different method names for Appearing/Disappearing.
BaseViewModel.cs:
public class BaseViewModel : MvxViewModel
{
...
// mvvmcross' MvxViewModel provides these.
protected override void ViewAppearing()
{
...
}
protected override void ViewDisappearing()
{
...
}
... Messenger Subscriptions methods ...
}