4

Does anyone know how to view an existing IMvxViewModel?

In my app, I have already created a bunch of ViewModels (PhotoViewModel) inside of another view model. They exist as a property on the parent ViewModel (AlbumViewModel). It would be very nice to just show a particular instance of a PhotoViewModel instead of creating a new instance of that view model when I want to view it.

public class AlbumViewModel : MvxViewModel {
    public ObservableCollection<PhotoViewModel> Photos
    {
        get { return GetValue(() => Photos); }
        set { SetValue(value, () => Photos); }
    }
}

public class PhotoViewModel : MvxViewModel { }

I was wondering if there was a way, other then creating my own IMvxViewModelLocator, to accomplish this task. I think having a protected method on the MvxNavigationObject called View could be really helpful both for new developers using the framework as well as performance. We'd be able to skip all of the reflection done currently to instantiate a view model.

Joe
  • 71
  • 1
  • 5

2 Answers2

2

The default ShowViewModel mechanism in MvvmCross uses page-based navigation - this navigation has to use Uris on WindowsPhone and Intents on Android.

Because of this, MvvmCross does not allow navigation by 'rich' objects - simple serialisable POCOs are Ok, but complicated 'rich' objects are not supported.

This is further essential because of 'tombstoning' - if your app/page/activity is later rehydrated then you cannot be sure of what historic View or ViewModel objects are actually in your history "back" stack.


If you want to navigate by rich object then the best way is to store those rich objects in a lookup service and to then navigate by some key/index into the lookup. However, I would personally call those lookedup objects Models rather than ViewModels (but the boundary does sometimes become blurred!)


Although based on MvvmCross v1 code, this question still gives quite a good background to this - What is the best way to pass objects to "navigated to" viewmodel in MVVMCross?

Some more up-to-date explanations include:


One final thing....

... the MvvmCross manifesto insists that MvvmCross is very open to customisation ...

Because of this you can override MvvmCross navigation and view model location if you want to. To do this, creating your own IMvxViewModelLocator would probably be a good way to start.

Community
  • 1
  • 1
Stuart
  • 66,722
  • 7
  • 114
  • 165
  • Stuart, thank you so much for your help. I was actually going down the road you described and I think I've come up with a solution that works fairly well. I've modified my question to show the proposed solution. Would love to hear your thoughts on it. Thanks again for all your help. – Joe Jul 26 '13 at 17:45
2

After some testing, below is a proposed solution. I'm not 100% in love with it, but it does work and provide the type developer experience I was looking for. So lets dig in.

To start, all of my ViewModels (VM) inherit from a base VM, AVM. This abstract base class supports looking up of an object as a public static method. It's a little gross, but it works well if you're willing to sip on the Kool-Aid. Below is the portion of the class that's relevant to this problem:

public abstract class AVM : MvxViewModel {
    private static readonly Dictionary<Guid, WeakReference> ViewModelCache = new Dictionary<Guid, WeakReference>();
    private static readonly string BUNDLE_PARAM_ID = @"AVM_ID";

    private Guid AVM_ID = Guid.NewGuid();
    private Type MyType;

    protected AVM()
    {
        MyType = this.GetType();
        ViewModelCache.Add(AVM_ID, new WeakReference(this));
    }

    public static bool TryLoadFromBundle(IMvxBundle bundle, out IMvxViewModel viewModel)
    {
        if (null != bundle && bundle.Data.ContainsKey(BUNDLE_PARAM_ID))
        {
            var id = Guid.Parse(bundle.Data[BUNDLE_PARAM_ID]);
            viewModel = TryLoadFromCache(id);
            return true;
        }

        viewModel = null;

        return false;
    }

    private static IMvxViewModel TryLoadFromCache(Guid Id)
    {
        if (ViewModelCache.ContainsKey(Id))
        {
            try
            {
                var reference = ViewModelCache[Id];
                if (reference.IsAlive)
                    return (IMvxViewModel)reference.Target;
            }
            catch (Exception exp) { Mvx.Trace(exp.Message); }
        }

        return null;
    }


    protected void View()
    {
        var param = new Dictionary<string, string>();
        param.Add(BUNDLE_PARAM_ID, AVM_ID.ToString());
        ShowViewModel(MyType, param);
    }

In order to get this all wired up, you have to create a custom view model locator. Here's the custom locator:

public class AVMLocator : MvxDefaultViewModelLocator
{
    public override bool TryLoad(Type viewModelType, IMvxBundle parameterValues, IMvxBundle savedState, out IMvxViewModel viewModel)
    {
        if (AVM.TryLoadFromBundle(parameterValues, out viewModel))
            return true;
        return base.TryLoad(viewModelType, parameterValues, savedState, out viewModel);
    }
}

Lastly you have to wire up. To do so, go into your App.cs and override CreateDefaultViewModelLocator like so:

    protected override IMvxViewModelLocator CreateDefaultViewModelLocator()
    {
        return new AVMLocator();
    }

You're all set. Now in any of your derived ViewModels that are already alive and well, you can do the following:

myDerivedVM.View();

There's still some more I need to do (like making sure the WeakReferences do their job and I don't have memory leaks and some additional error handling), but at the very least it's the experience I was going for. The last thing I did was add the following command to the AVM base class:

public MvxCommand ViewCommand
{
    get { return new MvxCommand(View); }
}

Now you can bind that command to any UI object and when invoked, it'll launch that view with that very instance of the VM.

Stuart, thanks for your help in steering me in the right direction. I'd be interested in hearing your feedback on the solution I provided. Thanks for all of your work with MVVMCross. It really is a very beautiful bit of code.

Cheers.

Joe
  • 71
  • 1
  • 5