Search code examples
mvvmxamarin.iosxamarinmvvmcross

MvvmCross: How to prevent casting ViewModel?


I noticed that in v3, MvvmCross removed the generic declaration <TViewModel> on MvxTouchViewController and renamed it to MvxViewController.

This means the ViewModel property is typed as a generic interface of IMvxViewModel rather than the specific TViewModel.

If I need to access the TViewModel in my view controller, is there a convenient way of getting the ViewModel already cast to the specific instance type for this View? Or do I have to cast it myself every time?


Solution

  • The previous Generic-based MvvmCross Views were removed from MvvmCross mainly because of the threat of 'Heizenbugs' in the objective-C based platforms.

    For what little anyone knows about Heizenbugs, see http://forums.xamarin.com/discussion/771/exporting-generic-type-to-objc-supported

    I don't believe I ever saw an Heizenbug, but Xamarin were very clear in their advice to avoid them at all costs - for example, they twice changed the compiler to issue errors for our generics. Indeed, on .Mac such generic code remains an error today, while on .iOS it's just a very scary warning.


    Added to this, we did also encounter some issues with Xaml based platforms when inheriting from Generic base classes - although these were mainly resolved (e.g. XamlParseException when I inherit a Page from a Generic base class)

    (Aside - to allow some backwards compatibility, WindowsPhone does still have some limited generic View support, but this is marked as Obsolete and I do regret allowing this to live on...)


    The good news is that, in my experience, the majority of Views do not need to know their ViewModel type - instead, the majority of Views can be built with 'pure bindings' without declaring a typed ViewModel.

    For those remaining Views which do need to know their ViewModel type, then a simple added property quickly adds this - e.g.:

    protected MyViewModel MyViewModel
    {
        get { return (MyViewModel)base.ViewModel; }
        /* set is optional - not typically needed 
        set { base.ViewModel = value; } 
        */
    }
    

    Alternatively you can probably write an extension method for this if you want to - e.g. something like:

    public static TViewModel TypedViewModel<TViewModel>(this IMvxView view) where TViewModel : class, IMvxViewModel
    {
        return view.ViewModel as TViewModel;
    }
    

    Very alternatively....

    .... If you are not scared of ghosts, goblins or Heizenbugs....

    One way to add the TypedViewModel property to all of your views would be to add generics back into your view hierarchy - this is easy for you to do - e.g. in Android adding

    public class BaseActivity<TViewModel> : MvxActivity 
       where TViewModel : class, IMvxViewModel
    {
        protected TViewModel TypedViewModel
        {
            get { return (TViewModel)base.ViewModel; }
            /* set is optional - not typically needed 
            set { base.ViewModel = value; }
            */
        }
    }
    

    This should work fine for you... but if you hit an Heizenbug, then I don't think anyone will be able to assist you. Xamarin have been very clear in recommending against this pattern - especially on the objC based platforms.