Whenever I call ShowViewModel, somehow a ViewModel and a View of the requested types are retrieved and are bound together for display on the screen. When are new instances of the ViewModel and View created versus looked up and retrieved from a cache somewhere? If new instances are always created and I choose to make my own cache to prevent multiple instances, then how do I show my cached ViewModel instance?
When are new instances of the ViewModel and View created versus looked up and retrieved from a cache somewhere?
Never - for new navigations the default behaviour is always to create new instances.
if... how do I show my cached ViewModel instance?
If for whatever reason you want to override the ViewModel location/creation, then there's information available about overriding the DefaultViewModelLocator in your App.cs in:
MVVMCross Passing values to ViewModel that has 2 constructors
http://slodge.blogspot.co.uk/2013/01/navigating-between-viewmodels-by-more.html
Put simply, implement your code:
public class MyViewModelLocator
: MvxDefaultViewModelLocator
{
public override bool TryLoad(Type viewModelType, IDictionary<string, string> parameterValueLookup,
out IMvxViewModel model)
{
// your implementation
}
}
then return it in App.cs:
protected override IMvxViewModelLocator CreateDefaultViewModelLocator()
{
return new MyViewModelLocator();
}
Note that older posts like How to replace MvxDefaultViewModelLocator in MVVMCross application are still conceptually compatible - but the details in those older posts are now out of date.
In MvvmCross v3.5 you can use this Class:
public class CacheableViewModelLocator : MvxDefaultViewModelLocator{
public override IMvxViewModel Load(Type viewModelType, IMvxBundle parameterValues, IMvxBundle savedState)
{
if (viewModelType.GetInterfaces().Any(x=>x == typeof(ICacheableViewModel)))
{
var cache = Mvx.Resolve<IMvxMultipleViewModelCache>();
var cachedViewModel = cache.GetAndClear(viewModelType);
if (cachedViewModel == null)
cachedViewModel = base.Load(viewModelType, parameterValues, savedState);
cache.Cache(cachedViewModel);
return cachedViewModel;
}
return base.Load(viewModelType, parameterValues, savedState);
}}
in your App Code override this method:
protected override IMvxViewModelLocator CreateDefaultViewModelLocator(){
return new CacheableViewModelLocator();}
Create an interface "ICacheableViewModel" and implement it on your ViewModel.
Now you can share the same ViewModel instance with multiple Views.
Related
I am trying to pass data from Activity A to Activity B but without using Intent putExtra nor using SharePreferences, I'm using a MVVM pattern in kotlin, so right now I'm using an object declaration like this
object SharedData{ var myMovies: ArrayList<Movie>? = null }
So later on in Activity A i'm assigning a value like this
val movieList = ArrayList<Movie>()
movieList.add(Movie("The Purge"))
SharedData.myMovies = movieList
And then in Activity B i retrieve this value by:
val movieList = ArrayList<Movie>()
SharedData.myMovies.let {
movieList = it
}
But I'm new in kotlin and now I know this is not the correct approach. because the singleton object allocates memory and it never gets collected by the GC. So now I'm stucked here.
Any guidance or help would be appreciated
So, if you're using MVVM pattern it's very straight forward. Use the basic ViewModel implementation with Android Architecture Components. See more of this in https://developer.android.com/topic/libraries/architecture/
class MyActivity : AppCompatActivity() {
private lateinit var myViewModel: MyViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.my_layout)
myViewModel = ViewModelProviders.of(this).get(MyViewModel::class.java)
myViewModel.myObject.observe(this, Observer { mySharedObject ->
//TODO whatever you want to do with your data goes here
Log.i("SomeTag", mySharedObject.anyData)
})
}
}
class MyCoachFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activity?.let {
myViewModel = ViewModelProviders.of(it).get(MyViewModel::class.java)
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val myObject = MyObject() // what ever object you need to share between Activities and Fragments, it could be a data class or any object
myViewModel.myObject.postValue(myObject)
}
}
class MyViewModel : ViewModel() {
var myObject = MutableLiveData<MyObject>()
}
My suggestion would be - If you want to share the data just between two activities, you should use intent and send the content as parcelable Object( parcelableArray for your Movielist scenario ) to the next activity. This would be clean implementation.
But I'm new in kotlin and now I know this is not the correct approach.
It is not wrong approach either, can be used depends on your use case. If it meets all the below scenarios, you can go for static variable approach. But Static object will be cleared when the app is killed (either by user or by system)
1.If the stored data size is less.
2.Data does not need to be persisted on App kill and relaunch.
3.Data is shared across many activities.
the singleton object allocates memory and it never gets collected by
the GC
Yes. It's true. Static variables are not eligible for garbage collection. But as long as the memory print is very less, it's okay to use static variable provided it meets above mentioned scenarios.
I'm using CSLA latest release and trying to add a row with default items to the collection. What I've noticed is the default constructor of the Foo class is called instead of the AddNewCore in the FooList Class. I am unable to get the AddNewCore or the Child_Create methods to get invoked when a new row is added in a XamDataGrid row. (A row is added, but it is from the default constructor of the FooLine Class--i.e. no default values and no MarkAsChild attribute.) Here is the code snippet that is in the FooList class:
protected override FooItem AddNewCore()
{
var item = DataPortal.CreateChild<FooItem>();
MarkAsChild();
Add(item);
return base.AddNewCore();
}
protected override void Child_Create()
{
var item = DataPortal.CreateChild<FooItem>();
MarkAsChild();
Add(item);
base.Child_Create();
}
What am I doing wrong?
AddNewCore() method exists in client side CSLA class 'ExtendedBindingList' with 'void' return type and same method is exists in server side class 'ObservableBindingList' with return type 'ListClass'. So we required to call run time client side method from server side.
Please refer below code for the same.
#if SILVERLIGHT
protected override void AddNewCore()
{
var item = DataPortal.CreateChild<FooItem>();
Add(item);
}
#endif
For information: The reason the above code does not work has to do with the way WPF invokes the New method. Typically, in other frameworks it is possible to hook on to that event, intercept it, and return with default data. With WPF, it is necessary to check the RecordAdding, or RecordAdded trigger events and process the invocations by hand.
In my case, the WPF would look like:
<i:Interaction.Triggers>'
i:EventTrigger EventName="RecordAdded">
<ei:CallMethodAction TargetObject="{Binding}"
MethodName="CreateDefaultAddressValuesCommand" />
</i:EventTrigger>
In the view model:
var idx = FooInformation.FooAddressList.Count - 1;
var address = await FooAddress.CreateAsync();
FooListing.FooAddressList[idx] = address;
I'm looking for the best way to inject a ViewModel into a Shell view.
I'm using Autofac (but I can adopt code from other IoC containers if sample is available). I have got the other VMs injecting correctly - but the method that resolves the VM using ResoleForPage method of the App class.
I'm fairly new to UWP developement and any help is greatly appreciated!
Passing a ViewModel to the Shell is indeed simpler than passing it to the other pages, because the Shell is the only page that is created explicitly by us: so, it should be enough to add a parameter to the constructor of the Shell of type ShellViewModel:
public Shell()
{
Instance = this;
this.InitializeComponent();
}
public Shell(INavigationService navService, ShellViewModel model) : this()
{
navigationMenu.NavigationService = navService;
navigationMenu.RefreshStyles(App.Current.RequestedTheme, true);
this.DataContext = model;
}
then expose the DataContext in a strongly typed way, as with any other pages (useful mainly if you use x:Bind bindings in xaml):
public ShellViewModel ViewModel => DataContext as ShellViewModel;
And now you just have to pass an instance of your ViewModel class, pulling it from your IoC container, when you create the Shell. In the latest Template 10 template for VS2017, it should be in the CreateRootElement method of the App class:
public override UIElement CreateRootElement(IActivatedEventArgs e)
{
var service = NavigationServiceFactory(BackButton.Attach, ExistingContent.Include);
return new Template10.Controls.ModalDialog
{
DisableBackButtonWhenModal = true,
Content = new Shell(service, new ShellViewModel()),
};
}
of course replacing new ShellViewModel() with the code to pull it from Autofac.
I have a WPF view that has a corresponding ViewModel. All instances are resolved via an unity container. Because I'm using prism I need two independent instances of the view to add it into two different regions the view is registered to. If I'd try to add one instance into both regions I get an
InvalidOperationException: Specified
element is already the logical child
of another element. Disconnect it
first.
when the view is added into the second region because it is already added to the first region.
This problem can easily be solved by using a TransientLifetimeManager that always returns a new instance so both regions would be filled with an independent instance.
But we have decided to create a child container when a new user logs on. Every session related view and view model are resolved using this child container. When the user's session ends, the child container is disposed so that also every session related instances are disposed. But using a TransientLifetimeManager the unity container cannot dispose those instances.
What we need is a lifetime manager that always returns a new instance, but is also capable of disposing those instances. Is there already such an lifetime manager around? Or is there another way to achieve what I described above?
What you want sounds like a variant of the ContainerControlledLifetime manager that does not maintain a singleton instance, but a collection of instances. Unfortunately this is not one of the built-in lifetime managers.
You can look at the code for the ContainerControlledLifetimeManager and see that it is pretty simple. Your "SynchronizedGetValue" implementation would always return null (signaling to the container that a new instance needs to be instantiated). You could just subclass ContainerControlledLifetimeManager and override that method.
I've pretty much written it. I suppose I could give you the code. :)
public class ContainerTrackedTransientLifetimeManager :
ContainerControlledLifetimeManager
{
protected override object SynchronizedGetValue()
{
return null;
}
}
That should work. I've not tested it... from the interface, it looks like it's designed for a 1 to 1 LifetimeManager to Object relationship, but if it turns out it is more than that, you might have to override SetValue (adds to a collection of objects) and dispose (disposes that collection of objects). Here's that implementation:
public class ContainerTrackedTransientLifetimeManager :
SynchronizedLifetimeManager, IDisposable
{
private ConcurrentCollection<object> values = new ConcurrentCollection<object>();
protected override object SynchronizedGetValue()
{
return null;
}
protected override void SynchronizedSetValue(object newValue)
{
values.Add(newValue);
}
public override void RemoveValue()
{
Dispose();
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected void Dispose(bool disposing)
{
var disposables = values.OfType<IDisposable>();
foreach(var disposable in disposables)
{
disposable.Dispose();
}
values.Clear();
}
I'm not sure which of these is the right answer. Let me know how it goes for you.
When you use transient lifetime manager (which is the default), Unity does not keep a reference to the created instance.
Thus, when there are no more reference to the instance, it will be GCed.
I am looking for some help and I hope that some good soul out there will be able to give me a hint :)
I am building a new application by using MVVM Light. In this application, when a View is created, it instantiates the corresponding ViewModel by using the MEF import.
Here is some code:
public partial class ContractEditorView : Window
{
public ContractEditorView ()
{
InitializeComponent();
CompositionInitializer.SatisfyImports(this);
}
[Import(ViewModelTypes.ContractEditorViewModel)]
public object ViewModel
{
set
{
DataContext = value;
}
}
}
And here is the export for the ViewModel:
[PartCreationPolicy(CreationPolicy.NonShared)]
[Export(ViewModelTypes.ContractEditorViewModel)]
public class ContractEditorViewModel: ViewModelBase
{
public ContractEditorViewModel()
{
_contract = new Models.Contract();
}
}
Now, this works if I want to open a new window in order to create a new contract... or in other words, it is perfect if I don't need to pass the ID of an existing contract.
However let's suppose I want to use the same View in order to edit an existing contract. In this case I would add a new constructor to the same View, which accepts either a model ID or a model object.
"Unfortunately" the ViewModel is created always in the same way:
[Import(ViewModelTypes.ContractEditorViewModel)]
public object ViewModel
{
set
{
DataContext = value;
}
}
As far as I know, this invokes the standard/no-parameters constructor of the corresponding ViewModel at composition-time.
So what I would like to know is how to differentiate this behavior? How can I call a specific constructor during composition time? Or how can I pass some parameters during the Import?
I really apologize if this question sounds silly, but I have only recently started to use MEF!
Thanks in advance,
Cheers,
Gianluca.
You CAN do this. Check out the Messenger implementation in MVVM-Light. You can pass a NotificationMessage(Of Integer) to send the right ID to the view model. The view model has to register for that type of message, and load it when a message is sent.
MEF Imports by default only have a parameterless constructor.