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);
}
Related
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 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
Imagine a WPF application with one main view representing a window that can be opened multiple times. This main view has several child views.
The messenger could send messages between the viewmodels of the main view and its child views' viewmodels.
Can the messenger be made to have a restricted scope so that if you had two main views open, interacting with one would message only that view's child views?
Is there another way to share an "identity" between the parent and child view models in a way that honours MVVM?
You can always choose between passing default IMessenger which will have static, application domain scope or creating new instance of messenger:
var domainWideMessenger = GalaSoft.MvvmLight.Messaging.Messenger.Default;
var localMessenger = new Messenger();
First approach is useful when you don't want to control scope of messenger. You can treat it as a central hub. "local" messengers are good for communications within VM or within some container.
Why this might be better then having tokens?! When you have advanced application with messaging built on tokens at some point you'll face difficulties handling them (picking the right one). Especially when it comes to dependency injection.
In your case you'll have new instance of Messenger per MainView which will be pushed down to all its child views and view models. To sync data and communicate between multiple instances of MainView use static Messenger from MVVM Light.
You could use the concept of "tokens" to achieve this effect.
The IMessenger interface has overloads of Register and Send that accept an object to restrict which registrants receive a message. If a message is sent with a token, the only objects that will see the message are those that registered for the message with the same token. Here, "same" means object equality, so you can use any object for the token that has sensible equality semantics and makes sense to you, i.e. a GUID, integer, or string.
As an example, consider the following objects:
public static class MessengerHelper
{
public static IMessenger Messenger { get { return GalaSoft.MvvmLight.Messaging.Messenger.Default; } }
public static object Group1Token { get { return 1; } }
public static object Group2Token { get { return 2; } }
}
public class FooChild
{
object token;
public FooChild(object token)
{
this.token = token;
MessengerHelper.Messenger.Register<IFooMessage>(this, token, HandleFooMessage);
}
void HandleFooMessage(IFooMessage fooMessage)
{
Console.WriteLine("FooChild got the message, token = " + (token ?? "(null)"));
}
}
public class FooParent
{
FooChild[] children;
public FooParent()
{
children = new [] {
new FooChild(MessengerHelper.Group1Token),
new FooChild(MessengerHelper.Group2Token),
new FooChild(null)
};
}
public void SendFooMessage(IFooMessage fooMessage, object token)
{
MessengerHelper.Messenger.Send(fooMessage, token);
}
}
Then if you create the parent and send a message with the given tokens:
FooParent parent = new FooParent();
parent.SendFooMessage(new FooMessage(), MessengerHelper.Group1Token);
parent.SendFooMessage(new FooMessage(), MessengerHelper.Group2Token);
You'll get the following output:
FooChild got the message, token = 1
FooChild got the message, token = 2
In your case, you'll want each main view models to have their own token, and pass their token to their child view models.
I'm trying to create a Portable Class such that I can use that across the platforms. It is working fine in Windows Phone 8.1 App. But when it comes to Android, then it is showing the Viewmodel as null and DataContext as Null in debugger which breaks the application debugger. When I create another viewmodel and view to test the app, its working fine on android too. What can be the possible reasons.
EDIT : It is crashing due to the constructor , in which I am passing the business Logic instance. So , Constructor is necessary i think but in that case it is crashing.I am not trying to resolve the ViewModel , i am trying to resolve the Service instance in ViewModel and for the purpose of MVVM, I am keeping the Service out from Droid Project so base.OnCreate(bundle) does not come into scene anyways.
public BookViewModel(ILogic _logic)
{
logic = _logic;
//var ss= Mvx.Resolve<ILogic>();
//var x = Mvx.CanResolve<ILogic>();
_details = logic.Read();
}
Below is the Logic Code :
public class Logic : ILogic
{
#region Attributes
List<Detail.Detail> _details = new List<Detail.Detail>();
DataLayer.DataLayer dl = new DataLayer.DataLayer();
#endregion
#region .ctor
public Logic()
{
populateList();
}
#endregion
#region Methods
private void populateList()
{
_details = dl.Access();
}
Below is the App.cs in ViewModel in which CanResolve is giving False
public class App : Cirrious.MvvmCross.ViewModels.MvxApplication
{
#region Methods
public override void Initialize()
{
Mvx.RegisterType<ILogic, Logic>();
var ss = Mvx.CanResolve<ILogic>();
RegisterAppStart<ViewModels.BookViewModel>();
}
#endregion
}
There are a few questions and answers around similar to this - e.g. similar to MVVMCross ViewModel construction failure notifications
The basic answer is that MvvmCross cannot resolve the ViewModel during the constructor - you have to wait until after the base.OnCreate(bundle) call - at this point the ViewModel will be resolved.
There's also a bit more about when ViewModel's are located in Who should create view model instances in MvvmCross and CoreDispatcher.HasThreadAccess "breaking change" (and probably a few other places too)
I have a user control of list of Patients which I use in other Views. However when I choose one of the Patients, the selection is propagated to all the views containing an instance of the user control. How can I make each view instantiate a new instance of the user control for each view?
I am using c#
Guessing from what you stated, I'd assume that you returning a static instance of you PatientViewModel from you locator. To solve this make sure that when the property is called a new instance of the view model is generated.
Edit: Locator with different instantiation methods
public class ViewModelLocator
{
public ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
//if (ViewModelBase.IsInDesignModeStatic)
//{
// // Create design time view services and models
// SimpleIoc.Default.Register<IDataService, DesignDataService>();
//}
//else
//{
// // Create run time view services and models
// SimpleIoc.Default.Register<IDataService, DataService>();
//}
SimpleIoc.Default.Register<MainViewModel>();
}
public MainViewModel Main
{
get
{
return ServiceLocator.Current.GetInstance<MainViewModel>();
}
}
public MainViewModel StaticMain
{
get
{
return _staticMain ?? (_staticMain = new MainViewModel());
}
}
private static MainViewModel _staticMain;
public MainViewModel NewMain
{
get
{
return new MainViewModel();
}
}
public MainViewModel NewIocMain
{
get
{
return ServiceLocator.Current.GetInstance<MainViewModel>(Guid.NewGuid().ToString());
}
}
public static void Cleanup()
{
// TODO Clear the ViewModels
}
}
The Main property uses Lauent's SimpleIoc container to create an instance. One of the properties of this container is, that it treats every type as singleton. Thus if you use this VM generation method you will share the VM throughout the views.
The StaticMain property does much the same, but instead of using Laurent's container it holds a static instance of the VM which is also shared between the views.
The NewMain property creates a new VM upon every call, therefore, the VM is not shared between the views.
The NewIocMain property also creates a new VM upon every call and the VM is, therefore, not shared between the views. However, the SimpleIoc container holds a reference to the instance created. It does not release this instance automatically and you have to call SimpleIoc.Default.Unregister(key) with the key you used for creation (the Guid) to remove the instance from the container once you no longer need it.
Instead of using the SimpleIoc you obviously can opt to use another IOC Container - such as Unity for example - that allows you greater control how your instances are created and how long they live. Barring this, I'd opt for the NewMain approach given yor case.