I am working on Xamrin Form right now. I have problem with clear data of ViewModel.
When I logout and login with different user, it shows me data of previous user because the value of UserProfileViewModel doesn't get clear.
When user logout, I want to clear user data from UserProfileViewModel class file. Currently I do this manually when user click on logout. I want any default method like dispose to clear all class member.
I have tried to inherit IDisposable interface with this.Dispose(); but that also didn't work.
I have also tried with default constructor as following but it throws error of
`System.TypeInitializationException`
on this line in app.xaml.cs: public static ViewModelLocator Locator => _locator ?? (_locator = new ViewModelLocator());
public UserProfileViewModel()
{
//initialize all class member
}
In given code, you can see that on Logout call, I call method
`ClearProfileData` of `UserProfileViewModel`
which set default(clear)
data. It is manually. I want to clear data when user logout.
View Model Logout Page
[ImplementPropertyChanged]
public class LogoutViewModel : ViewModelBase
{
public LogoutViewModel(INavigationService nService, CurrentUserContext uContext, INotificationService inService)
{
//initialize all class member
private void Logout()
{
//call method of UserProfileViewModel
App.Locator.UserProfile.ClearProfileData();
//code for logout
}
}
}
User Profile View Model
[ImplementPropertyChanged]
public class UserProfileViewModel : ViewModelBase
{
public UserProfileViewModel(INavigationService nService, CurrentUserContext uContext, INotificationService inService)
{
//initialize all class member
}
//Is there any other way to clear the data rather manually?
public void ClearProfileData()
{
FirstName = LastName = UserName = string.Empty;
}
}
ViewModel Locator
public class ViewModelLocator
{
static ViewModelLocator()
{
MySol.Default.Register<UserProfileViewModel>();
}
public UserProfileViewModel UserProfile => ServiceLocator.Current.GetInstance<UserProfileViewModel>();
}
Firstly there is no need to cleanup these kinds of primitive data types, the gc will do that for you.
However if you use Messages or any other Strong Reference for that matter you WILL have to Unsubscribe from them otherwise your viewmodal will hang around in memory and will never go out of scope
The garbage collector cannot collect an object in use by an
application while the application's code can reach that object. The
application is said to have a strong reference to the object.
With Xamarin it really depends how you are coupling your View to Viewmodals to determine which approach you might take to cleanup your viewmodals.
As it turns out MVVM Light ViewModelBase implements an ICleanup interface which has an overridable Cleanup method for you.
ViewModelBase.Cleanup Method
To cleanup additional resources, override this method, clean up and
then call base.Cleanup().
public virtual void Cleanup()
{
// clean up your subs and stuff here
MessengerInstance.Unregister(this);
}
Now your just left with where to call ViewModelBase.Cleanup
You can just call it when your View Closes, if you get a reference to the DataContext (I.e ViewModalBase) on the DataContextChanged Event
Or you can wire up a BaseView that plumbs this for you, or you can implement your own NagigationService which calls Cleanup on Pop. It really does depend on who is creating your views and viewmodels and how you are coupling them
Related
I really made a search for this topic and did not find anything, and because of that, I am asking the question here.
I have a WPF application with Prism installed.
I have wired the view-model with the view automatically by name convention
<UserControl x:Class="Views.ViewA"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:prism="http://prismlibrary.com/"
prism:ViewModelLocator.AutoWireViewModel="True">
and the model in the 'Model' like this
public class ViewAViewModel {
public ViewAViewModel () {
// time-resource consuming operations
}
}
the automatic binding work perfectly without a problem and the view and its corresponding view-model is matching, but the problem here.
I have a lot of those views say (50) and for every one of them, the view-model will be created with constructor exhausting the processes. This will make the startup of the application longer and also it will create a lot of view-models objects and put them in the RAM without being sure that they will be used at all.
What I need is to create the view-model class when the view is activated (I mean when the view is navigated to). Is this possible and if yes how?
Update
here is how I register the view with the Module, this is causing all the views to be created when the startup of the module.
public class Module1 : IModule
{
public void OnInitialized(IContainerProvider containerProvider)
{
var regionManager = containerProvider.Resolve<IRegionManager>();
regionManager.RegisterViewWithRegion("region1", typeof(View1));
regionManager.RegisterViewWithRegion("region1", typeof(View2));
is there any way to delay the creating of the views, until the navigation request come?
You could use navigation, for each view.
Or you must create an interfaces for your view and view model.
An example:
public interface IMyView
{
IMyViewModel ViewModel { get; set; }
}
public interface IMyViewModel
{
}
In the module or app.cs, in the method RegisterTypes you should register these.
containerRegistry.Register<IMyView, MyView>();
containerRegistry.Register<IMyViewModel, MyViewModel>();
You must implement IMyView interface in your MyView.cs class.
public partial class MyView : UserControl, IMyView
{
public MyView(IMyViewModel viewModel)
{
InitializeComponent();
ViewModel = viewModel;
}
public IMyViewModel ViewModel
{
get => DataContext as IMyViewModel;
set => DataContext = value;
}
}
After you could use it:
public void OnInitialized(IContainerProvider containerProvider)
{
var regionManager = containerProvider.Resolve<IRegionManager>();
var firstView = containerProvider.Resolve<IMyView>();
regionManager.AddToRegion(RegionNames.MainRegion, firstView);
}
In such case you shouldn't use ViewModelLocator.AutoWireViewModel in your view.
I am trying to build a WPF Prism bases app using MVVM design pattern.
When my app first starts, I want to require the user to login. Once logged in, I want to show the default landing page with the user name and buttons.
My thought, when a user login, I would publish an event called UserLoggedIn then on the home view-model, I would listen for that event. When the event is triggered, I would show the landing/home view.
So I created the event like so
public class UserLoggedIn : PubSubEvent<User>
{
}
Then in the LoginViewModel I handle the login and publish the event like so
private void HandleLogin(LoginView loginView)
{
try
{
User user = AuthenticationService.Authenticate(Username, loginView.GetPassport());
IUserPassport passport = PassportManager.Get(user.Username);
if (passport == null)
{
// Create a new session
passport = new UserPassport(new CustomIdentity(user), RegionManager);
}
// Assign the current session
PassportManager.SetCurrent(passport);
// Trigger the event
EventAggregator.GetEvent<UserLoggedIn>().Publish(passport);
// Deactivate the login view
RegionManager.GetMainRegion().Deactivate(loginView);
}
catch (Exception ex)
{
//Do something with the error
}
}
Finally in my HomeViewModel aka my landing view, I have the following code to listen for the UserLoggedIn event.
public class HomeViewModel : BindableBase
{
protected IUnityContainer Container { get; set; }
protected ICoreRegionManager RegionManager { get; set; }
private IEventAggregator EventAggregator { get; set; }
public HomeViewModel(IUnityContainer container, ICoreRegionManager regionManager, IEventAggregator eventAggregator)
{
Container = container;
RegionManager = regionManager;
EventAggregator = eventAggregator;
eventAggregator.GetEvent<UserLoggedIn>().Subscribe(ShowTheLangingPage);
}
private void ShowTheLangingPage(User user)
{
var homeView = Container.Resolve<HomeView>();
RegionManager.AddToMainRegion(homeView);
FullName = user.FirstName;
}
// I am using PropertyChange.Fody package, so this propery will automaticly raise the PropertyChange event.
public string FullName { get; set; }
}
Problem is the ShowTheLangingPage method never get triggered in my HomeViewModel as expected.
I made sure the the View HomeView and HomeViewModel are wired up correctly, by directly loading the HomeView on module initialization for testing.
Additionally, if add Container.Resolve<HomeView>(); just before I publish the event, the ShowTheLangingPage I called. Its like I have to resolve the HomeView manually for it to listen for the event.
How can I correctly listen for the UserLoggedIn event so i can show the corresponding view.
So I can learn the better/recommended way, is it better to show the landing view from the LoginViewModel instead of using event/listener.... and why? Also, if showing the landing view directly from the LoginViewModel then what is the recommended method to navigate; using Region.Add() method or RegionManager.RequestNavigate?
is it better to show the landing view from the LoginViewModel instead of using event/listener....
Yes.
and why?
Because that's what services (like the IRegionManager) are for, doing stuff for your view models and other services. Also, you have noticed, events can only be subscribed to by living objects.
Also, if showing the landing view directly from the LoginViewModel then what is the recommended method to navigate; using Region.Add() method or RegionManager.RequestNavigate?
If anything, a third class should listen for UserLoggedIn, but that's no gain over using the IRegionManager directly. In fact, it's even worse, because you have to artifically create this class. Side note: if you wait for the garbage collector after Container.Resolve<HomeView>(); and before logging in, you won't go to the landing page, because there's no subscriber (again).
I'm looking for the right way to dispose objects in a Xamarin Forms application. Currently i'm using XAML and MVVM coding style. Then from my view model i get a reference to a disposable object through the builtin service locator (DependencyService). Ideally i should be able to call Dispose() on the objects from my view model, but other solutions like attaching to ContentPage.OnDisappearing and NavigationPage.Popped could be feasible.
I had pretty much the same requirement a couple of weeks ago. I wanted to make sure that event subscriptions in my view models would be unsubscribed when the page is closed. After a lot of research my conclusion was that the simplest solution was to use the ContentPage.OnDisappearing method.
As you pointed out the object you want to dispose is in your ViewModel, so you need a little bit of infrastructure to make sure your ViewModel is informed when the it's disappearing. To do that I defined a base implementation of my view model that had two key methods OnAppearing and OnDisappearing (note this was a class rather than an interface because I have other base functionality such as IPropertyNotify implementation - not shown here).
public class ViewModelBase
{
/// <summary>
/// Called when page is appearing.
/// </summary>
public virtual void OnAppearing()
{
// No default implementation.
}
/// <summary>
/// Called when the view model is disappearing. View Model clean-up should be performed here.
/// </summary>
public virtual void OnDisappearing()
{
// No default implementation.
}
}
Then I subsclassed ContentPage and override the OnAppearing and OnDisappearing methods and then use them to notify my view model.
public class PageBase : ContentPage
{
/// <summary>
/// Performs page clean-up.
/// </summary>
protected override void OnDisappearing()
{
base.OnDisappearing();
var viewModel = BindingContext as ViewModelBase;
// Inform the view model that it is disappearing so that it can remove event handlers
// and perform any other clean-up required..
viewModel?.OnDisappearing();
}
protected override void OnAppearing()
{
base.OnAppearing();
// Inform the view model that it is appearing
var viewModel = BindingContext as ViewModelBase;
// Inform the view model that it is appearing.
viewModel?.OnAppearing();
}
}
Then when you implement a page just make sure that it is of type PageBase:
<?xml version="1.0" encoding="utf-8" ?>
<pages:PageBase xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:Forms.App.Controls;assembly=Forms.App"
xmlns:converters="clr-namespace:Forms.App.Converters;assembly=Forms.App"
xmlns:pages="clr-namespace:Forms.App.Pages;assembly=Forms.App"
x:Class="Forms.App.Pages.LogonPage"
NavigationPage.HasNavigationBar="False"
Title="Logon">
And in your ViewModel you can then override your OnDisappearing method and dispose your objects:
public class FormViewModel : ViewModelBase
{
public override void OnDisappearing()
{
base.OnDisappearing();
// Dispose whatever objects are neede here
}
}
Just one thing to watch out for - if you're using stack navigation the OnDisappearing method gets called when you stack another page on-top of your current page (your page is disappearing temporarily after all). So you will need to cater for this and probably not dispose your object in that case. However if you're not stacking anything on-top of your page there is nothing to worry about. In my case it was just event subscriptions so I attached the event handlers in the OnAppearing and detached them on the OnDisappearing.
I hope that helps you out!
We were getting disposed of object exceptions in Forms when Bindings to ListViews or Labels changed values as pages/fragments were being disposed of. I'm assuming you could dispose of objects in your ViewModel the same place we were removing bindings.
protected override void OnParentSet()
{
base.OnParentSet();
if (Parent == null)
{
//Clear a bunch of bindings or dispose of ViewModel objects
BindingContext =
_listView.ItemsSource = null;
}
}
I have View Models that conform to IDisposable. So I needed a way for the Page's BindingContext to be disposed when the page is no longer needed.
I used the suggestion of Nick which uses OnParentSet being set to NULL to known when the page is no longer needed.
The SafeContentPage class can be used in place of ContentPage. Iff The binding context supports IDisposable will it automatically try to dispose the binding context.
public class SafeContentPage : ContentPage
{
protected override void OnParentSet()
{
base.OnParentSet();
if (Parent == null)
DisposeBindingContext();
}
protected void DisposeBindingContext()
{
if (BindingContext is IDisposable disposableBindingContext) {
disposableBindingContext.Dispose();
BindingContext = null;
}
}
~SafeContentPage()
{
DisposeBindingContext();
}
}
The OnDisappearing method isn't a reliable technique as there are platform differences in terms of when it is called, and just because the page disappeared doesn't mean its View Model is no longer needed.
My current implementation of passing UserID in my application is through the constructor.
i.e. SomeObject s = new SomeObject(userID)
Where in there is a code behind that does things based on the userID. The userID is further keep tracked by adding another property named "CurrentUser", however this seems to be a dirty solution as I have to implement it to all ViewModels and it seems to violate the "DRY" concept.
The second approach I have in mind is creating a public static variable on my MainWindowViewModel where all my other models can refer to it as MainWindowViewModel.CurrentUser.
Is one of the two approach the correct way to do this or is there a better approach that i don't know about?
You need to carefully analyze up front what you want to achieve with your application. Are you happy with there only ever being one selected client? Or will you need to have multiple clients being viewed or edited at a time (i.e. you have an MDI style app)?
Going with the single client approach is easy, you can implement the global property bag as already mentioned in other answers. But I will advise caution: if you build your app on the assumption there will only ever be one selected client it becomes a real PITA to try to refactor to make it multi-client capable. Using a centralized property bag or "session service" like this is indeed decoupling state from the VM, but the centralized service can still turn into a monstrosity over time and you build up too much dependence on it.
If you do want to go the multi-client route, then you are on the right track - but instead of passing a client identifier in on the constructor, pass (inject) the entire client data object. The chances are that you already have most of the client details available from the piece of UI that invokes the client oriented VM, so pass it in and save having to make another trip to your database to get the details.
Don't tie a current user to a ViewModel. I typically opt for a SessionService of some kind. If you're using Dependency Injection (DI), register a singleton of an ISessionService and concrete implementation. If your not using DI, then just have your app start create a singleton, like a SessionService.Current. Then you can put any items you need in here. Then each ViewModel can ask for the SessionService.Current.User and they have it. Your ViewModels shouldn't know about each other, but they can know about services. This keeps it DRY and loosely coupled, especially if you only access these session variables using the interface of an ISessionService and not the concrete implementation. This allows you to mock one up very easily without changing any ViewModel code.
What you have here is the problem of Communication between ViewModels. There are a number of solutions but my fave is the Mediator Pattern:
using System;
namespace UnitTestProject2
{
public class GetDataViewModel
{
IMediator mediator;
public GetDataViewModel(IMediator mediator)
{
this.mediator = mediator;
this.mediator.ListenFor("LoggedIn", LoggedIn);
}
protected string UserId;
protected void LoggedIn(Object sender, EventArgs e)
{
UserId = sender.ToString();
}
}
public class LoginViewModel
{
IMediator mediator;
public LoginViewModel(IMediator mediator)
{
this.mediator = mediator;
}
public string UserId { get; set; }
public void Login(string userid)
{
this.UserId = userid;
this.mediator.RaiseEvent("LoggedIn", this.UserId);
}
}
public interface IMediator
{
public void ListenFor(string eventName, EventHandler action );
public void RaiseEvent(string eventName, object data);
}
}
I Haven't implemented the Mediator here, because it can get quite involved and there are a number of packages available. but you can see the idea from my simple interface. Essentially the Mediator provides a Global list of EventHandlers which any Viewmodel can call or add to. You still have the problem of where to store the event names. Its nice to have these in enums, but that gives you a coupling problem. (a problem I usually ignore)
Alternatively you can have a Controller or (MasterViewModel if you love MVVM)
using System;
namespace UnitTestProject3
{
public class GetDataViewModel
{
protected string UserId;
public void LoggedIn(Object sender, EventArgs e)
{
UserId = sender.ToString();
}
}
public class LoginViewModel
{
public EventHandler OnLogin;
public string UserId { get; set; }
public void Login(string userid)
{
this.UserId = userid;
if (this.OnLogin != null)
{
this.OnLogin(this.UserId, null);
}
}
}
public class Controller // or MasterViewModel
{
public void SetUp()
{
GetDataViewModel vm1 = new GetDataViewModel();
LoginViewModel vm2 = new LoginViewModel();
vm2.OnLogin += vm1.LoggedIn;
//wire up to views and display
}
}
}
I am writing Windows Phone chat application and I would like to implement ChatPage which will be used to hold on a conversation view. The whole idea is to create view as much similar to SMS page as it is possible, and switch between instances of that page.
And here comes the problem - on each instance of page I need to bind the Contact and ContactMessages which are stored in XML file. I think that getting proper messages from XML file for Contact can be simply written in constructor. But how to open new instance of page and send it a Contact.
I am using MVVM Light toolkit and I could use Messanger to do it but how can I get sure that my Contact wont be registered in other instances of page. Registering an instance of ChatPageViewModel in that case can't be realized in ViewModelLocator because I need to use multiple instances of that ViewModel (reprimand me if I am wrong, I am newbie in MVVM light).
Is any way to achieve that? Or maybe the way I am thinking about that is totally wrong?
You can try to make a Service with a interface. And when the customer click on the contact, pass the parameters that this service can search an return the messages with the Contact. And every ViewModel that need this service you can inyected it from the constructor. Only you need some parameter to know which xml to read.
I dont know if a IoC container. Unity or Autofac or others can solve your problem too.
If you use Autofac you can have a similar class than this:
public class ViewModelLocator
{
private IContainer container;
public ViewModelLocator()
{
ContainerBuilder builder = new ContainerBuilder();
builder.RegisterType<MainViewModel>();
container = builder.Build();
}
public MainViewModel MainViewModel
{
get { return container.Resolve<MainViewModel>(); }
}
}
And you can work with this instances like this parameters:
builder.RegisterType<MainViewModel>().InstancePerDependency();
builder.RegisterType<MainViewModel>().InstancePerLifetimeScope();
builder.RegisterType<MainViewModel>().SingleInstance();
for more info here for Autofac and here for Unity
Hope helps. Greetings!
I'd go for this scenario, based on MVVM Light:
On a central page you have a list of all your contacts. As soon as the user selects one contact you broadcast a message that a contact has been selected. The conversation viewmodel subscribes to this message and loads the conversation from the model. The contacts viewmodel uses a navigation service to navigate to the conversation view.
(Part of) The contacts ViewModel:
public ContactsViewModel(IDataService dataService, INavigationService navigationService)
{
_dataService = dataService;
_navigationService = navigationService;
}
private RelayCommand _showConversation;
public RelayCommand ShowEvents
{
get
{
return _showConversation ?? (_showConversation = new RelayCommand(ExecuteShowConversation));
}
}
private void ExecuteShowConversation()
{
Messenger.Default.Send(new ContactSelectedMessage(){Contact = Contact);
_navigationService.NavigateTo(new Uri("/Views/ConversationView.xaml", UriKind.RelativeOrAbsolute));
}
(Part of) the Conversation ViewModel:
public ConversationViewModel(IDataService dataService, INavigationService navigationService)
{
_dataService = dataService;
_navigationService = navigationService;
Messenger.Default.Register<ContactSelectedMessage>(this, OnContactSelected);
}
private void OnContactSelected(ContactSelectedMessage contactSelectedMessage)
{
_dataService.GetConversation(contactSelectedMessage.Contact);
}
In order for the viewmodel to receive messages it should be instantiated before the message is broadcast. So, you must instantiate it in the ViewModelLocator.
In the ViewModelLocator:
static ViewModelLocator()
{
/// parts missing for brevity
SimpleIoc.Default.Register<IDataService, DataService>();
SimpleIoc.Default.Register<INavigationService, NavigationService>();
SimpleIoc.Default.Register<ContactsViewModel>();
SimpleIoc.Default.Register<ConversationViewModel>(true);
}