Ertay Shashko
Ertay Shashko

Reputation: 1257

Multiple Views for single ViewModel (iOS)

I am aware that in order to achieve this, I need to create a custom presenter and map the ViewModel to my Views manually by overriding the InitializeViewLookup method. I successfully did this on Android and so far it works fine, but I can't seem to do it on iOS. Here's what I tried:

public override void Show(MvxViewModelRequest request)
    {
        // check if there are any presentation values
        if (request.PresentationValues != null)
        {
            // if yes, check if one of them is SelectedView
            if (request.PresentationValues.ContainsKey(SingletonViewModelLocator.SelectedView))
            {
                UIViewController viewController;
                switch (request.PresentationValues[SingletonViewModelLocator.SelectedView])
                {
                    // The ViewModel requested the First View, load that one
                    case SingletonViewModelLocator.FirstViewValue:
                        viewController = new FirstView();
                        MasterNavigationController.PushViewController(viewController, true);
                        return;
                    // The ViewModel requested the Second View, load that one
                    case SingletonViewModelLocator.SecondViewValue:
                        viewController = new SecondView();
                        MasterNavigationController.PushViewController(viewController, true);
                        return;
                    // wrong view requested
                    default:
                        throw (new InvalidEnumArgumentException(request.PresentationValues[SingletonViewModelLocator.SelectedView] +
                                                                " does not exist."));
                }
            }
        }
        // otherwise run the default method which means 1 ViewModel is mapped to 1 View
        base.Show(request);
    }

Here's InitializeViewLookup:

protected override void InitializeViewLookup()
    {
        var container = Mvx.Resolve<IMvxViewsContainer>();
        container.Add(typeof(MainViewModel), typeof(MainView));
        // TheViewModel is mapped to two Views
        container.Add(typeof(TheViewModel), typeof(FirstView));
        container.Add(typeof(TheViewModel), typeof(SecondView));
    }

This gives an "Object reference not set to an instance of an object" exception somewhere inside Mvx libraries on the ViewDidLoad method when the navigation occurs.

Just for reference, here's how I'm doing it on Android which works fine.

public override void Show(MvxViewModelRequest request)
    {
        // check if there are any presentation values
        if (request.PresentationValues != null)
        {
            // if yes, check if one of them is SelectedView
            if (request.PresentationValues.ContainsKey(SingletonViewModelLocator.SelectedView))
            {
                var activity = Activity;
                Intent intent;
                switch (request.PresentationValues[SingletonViewModelLocator.SelectedView])
                {
                    // The ViewModel requested the First View, load that one
                    case SingletonViewModelLocator.FirstViewValue:
                        intent = new Intent(activity, typeof (FirstView));
                        Show(intent);
                        return;
                    // The ViewModel requested the Second View, load that one
                    case SingletonViewModelLocator.SecondViewValue:
                        intent = new Intent(activity, typeof (SecondView));
                        Show(intent);
                        return;
                    // wrong view requested
                    default:
                        throw (new InvalidEnumArgumentException(request.PresentationValues[SingletonViewModelLocator.SelectedView] +
                                                                " does not exist."));
                }
            }
        }
        // otherwise run the default method which means 1 ViewModel is mapped to 1 View
        base.Show(request);
    }

EDIT

Here's the stack trace:

0x0 in Cirrious.MvvmCross.ViewModels.MvxViewModelLoader.LoadViewModel   
0x65 in Cirrious.MvvmCross.Touch.Views.MvxViewControllerExtensionMethods.LoadViewModel  
0x13 in Cirrious.MvvmCross.Views.MvxViewExtensionMethods.OnViewCreate   
0xE in Cirrious.MvvmCross.Touch.Views.MvxViewControllerExtensionMethods.OnViewCreate    
0x7 in Cirrious.MvvmCross.Touch.Views.MvxViewControllerAdapter.HandleViewDidLoadCalled  
0xB in Cirrious.CrossCore.Core.MvxDelegateExtensionMethods.Raise    
0xD in Cirrious.CrossCore.Touch.Views.MvxEventSourceViewController.ViewDidLoad  

0x2 in Demo.iOS.FirstView.ViewDidLoad at c:[path]\View\FirstView.cs:34,-1
0xA6 in UIKit.UIApplication.UIApplicationMain
0xB in UIKit.UIApplication.Main at /Developer/MonoTouch/Source/monotouch/src/UIKit/UIApplication.cs:62,4
0x3B in UIKit.UIApplication.Main at /Developer/MonoTouch/Source/monotouch/src/UIKit/UIApplication.cs:46,4
0x8 in Demo.iOS.Application.Main at c:[path]\Main.cs:17,-1

Upvotes: 1

Views: 1299

Answers (1)

Stuart
Stuart

Reputation: 66882

The default Container in an Mvx Touch application creates views using code like:

            CurrentRequest = request;
            var viewType = GetViewType(request.ViewModelType);
            if (viewType == null)
                throw new MvxException("View Type not found for " + request.ViewModelType);

            var view = CreateViewOfType(viewType, request);
            view.Request = request;
            return view;

from https://github.com/MvvmCross/MvvmCross/blob/1eeea41e110934e52bf7e6682d8751b885206844/Cirrious/Cirrious.MvvmCross.Touch/Views/MvxTouchViewsContainer.cs#L31

Each View then loads/locates its own ViewModel using instructions hidden in the Request.

If the Request is empty, then Mvx defaults to some heuristics to create a default ViewModel of a type based on a convention using the View's class name.


In your case, I think the best solution is to set the Request property on your ViewController - e.g.

                case SingletonViewModelLocator.SecondViewValue:
                    viewController = new SecondView() { Request = request };
                    MasterNavigationController.PushViewController(viewController, true);
                    return;

However, you could also do this by providing a hint in SecondView about the type of ViewModel that is expected... you can do that by overriding the ViewModel property with a type, by inheriting from MvxViewController<T>, by providing an attribute hint for the view model type (MvxViewFor), or by adding some special lookups during Setup

Note - your question also suggests to me that your doing some singleton magic on the ViewModel lookup side, so you may need to adjust this answer to match whatever that is...

Upvotes: 1

Related Questions