0
votes

I want to move from one screen to next using viewmodels in mvvmcross and pass some model to next ViewModel. But I am getting following crash:

MvvmCross.Platform.Exceptions.MvxException: Failed to construct and initialize ViewModel for type iManage.ViewModels.LoginViewModel from locator MvxDefaultViewModelLocator - check InnerException for more information ---> MvvmCross.Platform.Exceptions.MvxException: Problem creating viewModel of type LoginViewModel ---> MvvmCross.Platform.Exceptions.MvxIoCResolveException: Failed to resolve parameter for parameter item of type SchoolModel when creating iManage.ViewModels.LoginViewModel at MvvmCross.Platform.IoC.MvxSimpleIoCContainer.GetIoCParameterValues (System.Type type, System.Reflection.ConstructorInfo firstConstructor) [0x00066] in <6adc0d5857264558a9d45778a78ae02a>:0 at MvvmCross.Platform.IoC.MvxSimpleIoCContainer.IoCConstruct (System.Type type) [0x0002c] in <6adc0d5857264558a9d45778a78ae02a>:0 at MvvmCross.Platform.Mvx.IocConstruct (System.Type t) [0x00006] in <6adc0d5857264558a9d45778a78ae02a>:0 at MvvmCross.Core.ViewModels.MvxDefaultViewModelLocator.Load (System.Type viewModelType, MvvmCross.Core.ViewModels.IMvxBundle parameterValues, MvvmCross.Core.ViewModels.IMvxBundle savedState) [0x00000] in :0 --- End of inner exception stack trace --- at MvvmCross.Core.ViewModels.MvxDefaultViewModelLocator.Load (System.Type viewModelType, MvvmCross.Core.ViewModels.IMvxBundle parameterValues, MvvmCross.Core.ViewModels.IMvxBundle savedState) [0x00029] in :0 at MvvmCross.Core.ViewModels.MvxViewModelLoader.LoadViewModel (MvvmCross.Core.ViewModels.MvxViewModelRequest request, MvvmCross.Core.ViewModels.IMvxBundle savedState) [0x00035] in :0 --- End of inner exception stack trace --- at MvvmCross.Core.ViewModels.MvxViewModelLoader.LoadViewModel (MvvmCross.Core.ViewModels.MvxViewModelRequest request, MvvmCross.Core.ViewModels.IMvxBundle savedState) [0x00068] in :0 at MvvmCross.iOS.Views.MvxViewControllerExtensionMethods.LoadViewModel (MvvmCross.iOS.Views.IMvxIosView iosView) [0x0005f] in <6f99728979034e579bc72f6d53e5bc35>:0 at MvvmCross.Core.Views.MvxViewExtensionMethods.OnViewCreate (MvvmCross.Core.Views.IMvxView view, System.Func`1[TResult] viewModelLoader) [0x00012] in :0 at MvvmCross.iOS.Views.MvxViewControllerExtensionMethods.OnViewCreate (MvvmCross.iOS.Views.IMvxIosView iosView) [0x00001] in <6f99728979034e579bc72f6d53e5bc35>:0 at MvvmCross.iOS.Views.MvxViewControllerAdapter.HandleViewDidLoadCalled (System.Object sender, System.EventArgs e) [0x00007] in <6f99728979034e579bc72f6d53e5bc35>:0 at at (wrapper delegate-invoke) :invoke_void_object_EventArgs (object,System.EventArgs) at MvvmCross.Platform.Core.MvxDelegateExtensionMethods.Raise (System.EventHandler eventHandler, System.Object sender) [0x00003] in <6adc0d5857264558a9d45778a78ae02a>:0 at MvvmCross.Platform.iOS.Views.MvxEventSourceViewController.ViewDidLoad () [0x00006] in <4467c42ffcc4478e847227b8e4af47fe>:0 at MvvmCross.iOS.Views.MvxViewController.ViewDidLoad () [0x00001] in <6f99728979034e579bc72f6d53e5bc35>:0 at iManage.iOS.Views.LoginView.ViewDidLoad () [0x00001] in /Users/pankajsachdeva/Projects/iManage/iOS/Views/LoginView.cs:18 at at (wrapper managed-to-native) UIKit.UIApplication:UIApplicationMain (int,string[],intptr,intptr) at UIKit.UIApplication.Main (System.String[] args, System.IntPtr principal, System.IntPtr delegate) [0x00005] in /Users/builder/data/lanes/5665/f70a1348/source/xamarin-macios/src/UIKit/UIApplication.cs:79 at UIKit.UIApplication.Main (System.String[] args, System.String principalClassName, System.String delegateClassName) [0x00038] in /Users/builder/data/lanes/5665/f70a1348/source/xamarin-macios/src/UIKit/UIApplication.cs:63 at iManage.iOS.Application.Main (System.String[] args) [0x00001] in /Users/pankajsachdeva/Projects/iManage/iOS/Main.cs:17

From ViewModel Code:

public class SchoolSelectionViewModel : BaseViewModel
{
    private readonly ISchoolNames _schoolService;
    public SchoolSelectionViewModel(ISchoolNames schoolService)
    {
        _schoolService = schoolService;
    }
    public override void Start()
    {
        IsLoading = true;
        _schoolService.GetFeedItems(OnDilbertItems, OnError);
    }

    private void OnDilbertItems(List<SchoolModel> list)
    {
        IsLoading = false;
        Items = list;
    }

    private void OnError(Exception error)
    {
        // not reported for now
        IsLoading = false;
    }

    private List<SchoolModel> _items = new List<SchoolModel>();
    public List<SchoolModel> Items
    {
        get { return _items; }
        set { _items = value; RaisePropertyChanged(() => Items); }
    }
    private MvxCommand<SchoolModel> _itemSelectedCommand;
    public ICommand ItemSelectedCommand
    {
        get
        {
            _itemSelectedCommand = _itemSelectedCommand ?? new MvvmCross.Core.ViewModels.MvxCommand<SchoolModel>(DoSelectItem);
            return _itemSelectedCommand;
        }
    }

    private void DoSelectItem(SchoolModel item)
    {
        //ShowViewModel<LoginViewModel>(item);
        ShowViewModel<LoginViewModel>(new LoginViewModel(item));
    }
}

To ViewModel Code:

public class LoginViewModel : BaseViewModel
{
    private readonly ILoginService _loginService;

    private readonly IDialogService _dialogService;

    public LoginViewModel(SchoolModel item)
    {
        //_loginService = loginService;
        //_dialogService = dialogService;
        School = item;
        Username = "TestUser";
        Password = "YouCantSeeMe";
        IsLoading = false;
    }

    private SchoolModel _school;
    public SchoolModel School
    {
        get
        {
            return _school;
        }

        set
        {
            SetProperty(ref _school, value);
            RaisePropertyChanged(() => School);
        }
    }

    private string _username;
    public string Username
    {
        get
        {
            return _username;
        }

        set
        {
            SetProperty(ref _username, value);
            RaisePropertyChanged(() => Username);
        }
    }

    private string _password;
    public string Password
    {
        get
        {
            return _password;
        }

        set
        {
            SetProperty(ref _password, value);
            RaisePropertyChanged(() => Password);
        }
    }

    private IMvxCommand _loginCommand;
    public virtual IMvxCommand LoginCommand
    {
        get
        {
            _loginCommand = _loginCommand ?? new MvxCommand(AttemptLogin, CanExecuteLogin);
            return _loginCommand;
        }
    }

    private void AttemptLogin()
    {
        if (_loginService.Login(Username, Password))
        {
            ShowViewModel<DashboardStdViewModel>();
        }
        else
        {
            _dialogService.Alert("We were unable to log you in!", "Login Failed", "OK");
        }
    }

    private bool CanExecuteLogin()
    {
        return (!string.IsNullOrEmpty(Username) || !string.IsNullOrWhiteSpace(Username))
               && (!string.IsNullOrEmpty(Password) || !string.IsNullOrWhiteSpace(Password));
    }
}

Edit1: Modified following in my from ViewModel:

        private async void DoSelectItem(SchoolModel item)
    {
        await _navigationService.Navigate<LoginViewModel,SchoolModel>(item);
    }

changed next ViewModel declaration as following:

public class LoginViewModel : MvxViewModel<SchoolModel>

It is still crashing when I try to show next viewmodel with following error: Object reference not set to an instance of an object.
Edit2: Complete Error:

System.NullReferenceException: Object reference not set to an instance of an object at iManage.ViewModels.SchoolSelectionViewModel+d__19.MoveNext () [0x0000f] in /Users/pankajsachdeva/Projects/iManage/iManage/ViewModels/SchoolSelectionViewModel.cs:67 at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () [0x0000c] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.6.1.3/src/mono/mcs/class/referencesource/mscorlib/system/runtime/exceptionservices/exceptionservicescommon.cs:152 at System.Runtime.CompilerServices.AsyncMethodBuilderCore+<>c.b__6_0 (System.Object state) [0x00000] in /Library/Frameworks/Xamarin.iOS.framework/Versions/11.6.1.3/src/mono/mcs/class/referencesource/mscorlib/system/runtime/compilerservices/AsyncMethodBuilder.cs:1018 at UIKit.UIKitSynchronizationContext+c__AnonStorey0.<>m__0 () [0x00000] in /Users/builder/data/lanes/5665/f70a1348/source/xamarin-macios/src/UIKit/UIKitSynchronizationContext.cs:24 at Foundation.NSAsyncActionDispatcher.Apply () [0x00000] in /Users/builder/data/lanes/5665/f70a1348/source/xamarin-macios/src/Foundation/NSAction.cs:163 at at (wrapper managed-to-native) UIKit.UIApplication:UIApplicationMain (int,string[],intptr,intptr) at UIKit.UIApplication.Main (System.String[] args, System.IntPtr principal, System.IntPtr delegate) [0x00005] in /Users/builder/data/lanes/5665/f70a1348/source/xamarin-macios/src/UIKit/UIApplication.cs:79 at UIKit.UIApplication.Main (System.String[] args, System.String principalClassName, System.String delegateClassName) [0x00038] in /Users/builder/data/lanes/5665/f70a1348/source/xamarin-macios/src/UIKit/UIApplication.cs:63 at iManage.iOS.Application.Main (System.String[] args) [0x00001] in /Users/pankajsachdeva/Projects/iManage/iOS/Main.cs:17

1
This is why ViewModel navigation just don't make any sense lol.Michael Puckett II
Where is the "Object reference not set to an instance of an object" coming from? Could you attach the full exception stack trace? What version of MvvmCross are you using?Plac3Hold3r
please check my editpankaj
What is on line 67 of SchoolSelectionViewModel.cs? It seems that is where you will find your null referencePlac3Hold3r
await _navigationService.Navigate<LoginViewModel,SchoolModel>(item); item is an object of type SchoolModel, It has data and is not nullpankaj

1 Answers

2
votes

MvvmCross.Platform.Exceptions.MvxIoCResolveException: Failed to resolve parameter for parameter item of type SchoolModel when creating iManage.ViewModels.LoginViewModel at MvvmCross.Platform.IoC.MvxSimpleIoCContainer.GetIoCParameterValues (System.Type type, System.Reflection.ConstructorInfo firstConstructor)

The issue is with the type of the parameter SchoolModel used in the constructor of LoginViewModel. The MvxSimpleIoCContainer can not resolve the type when trying to construct LoginViewModel.

So there are two items I will address

  1. Why are you getting this exception, by looking at how MvvmCross constructs view models.
  2. How to pass parameters between view models using the navigation service introduced in MvvmCross 5 and the traditional ShowViewModel with MvvmCross 5 lifecycle.

View Model IoC usage in MvvmCross

In MvvmCross view models are constructed via IoC. Meaning that the parameters of the public constructor of a view model class needs to be registered with the IoC container in order for the container to construct the class.

The types used as construction parameters are also important as by default MvvmCross uses the Service Locator pattern support only interface types registered against an implementation type. An example of registering a implementation type Foo against and interface type IFoo.

Mvx.ConstructAndRegisterSingleton<IFoo, Foo>();

Due to MvvmCross using Constructor Injection for view models you can not use the public constructor of a view model class for your own parameter passing to view model class instances. Constructor parameters must be able to come from the IoC container.


MvvmCross 5 passing parameters between View Models (Navigation Service)

In MvvmCross 5 the Navigation Service was introduced as the preferred means to write navigation logic within MvvmCross. The Navigation Service also providers a means to pass parameters between view models to avoid passing via view model constructors. There is a nice example in the MvvmCross documentation showing how to pass parameters. Here is an extract

public class MyViewModel : MvxViewModel
{
    private readonly IMvxNavigationService _navigationService;

    public MyViewModel(IMvxNavigationService navigationService)
    {
        _navigationService = navigationService;
    }

    public async Task SomeMethod()
    {
        await _navigationService.Navigate<NextViewModel, MyObject>(new MyObject());
    }
}

public class NextViewModel : MvxViewModel<MyObject>
{
    private MyObject _myObject;

    public override void Prepare(MyObject parameter)
    {
        // receive and store the parameter here
        _myObject = parameter;
    }
}

A quick explanation. Starting with the NextViewModel, the view model navigation to, you need to specify the type of the parameter being passed in the inherited MvxViewModel base class generic and then override Prepare to pass through the parameter. In the calling class MyViewModel for the navigate method you include the type of the view model navigating to as well as the type of the parameter you are passing. Then you pass the parameter as the first argument of the navigate method.


Passing parameters between View Models (ShowViewModel)

In your sample code you are still using the older ShowViewModel pattern for navigation, so I'll include how you can pass parameters using the ShowViewModel approach. The MvvmCross documentation for passing parameter for ShowViewModel can be found here for the complex parameter object and here for the simple parameter object.

Complex parameter object

For complex parameter objects MvvmCross requires that you have MvvmCross Json plugin installed. This approach will serialize and deserialize the parameter being passed through

public class SchoolSelectionViewModel : BaseViewModel
{
    public void DoSelectItem(SchoolModel item)
    {
        ShowViewModel<LoginViewModel, SchoolModel>(item);
    }
}

public class LoginViewModel : BaseViewModel<SchoolModel>
{
    private SchoolModel _parameter;

    public override Task Prepare(SchoolModel parameter)
    {
        // receive and store the parameter here
        _parameter = parameter;
    }
}

You may also need to implement custom a base class using the IMvxViewModel<TParameter> contract if you cannot have your LoginViewModel inherit for MvxViewModel<TParameter>.

public abstract class BaseViewModel<TParameter> : BaseViewModel, IMvxViewModel<TParameter>
{
    public async Task Init(string parameter)
    {
        if (!string.IsNullOrEmpty(parameter))
        {
            IMvxJsonConverter serializer;
            if (!Mvx.TryResolve(out serializer))
            {
                throw new MvxIoCResolveException("There is no implementation of IMvxJsonConverter registered. You need to use the MvvmCross Json plugin or create your own implementation of IMvxJsonConverter.");
            }

            var deserialized = serializer.DeserializeObject<TParameter>(parameter);
            Prepare(deserialized);
            await Initialize();
        }
    }

    public abstract void Prepare(TParameter parameter);
}

Simple parameter object

For basic types that do not require serialization this approach can be used at the expense of loss of type safety parameter.

public class SchoolSelectionViewModel : BaseViewModel
{
    public void DoSelectItem(SchoolModel item)
    {
        ShowViewModel<LoginViewModel>(item);
    }
}

public class LoginViewModel : BaseViewModel
{
    private SchoolModel _parameter;

    public override void Init(SchoolModel parameter)
    {
        // receive and store the parameter here
        _parameter = parameter;
    }
}

Note rules for type used with Simple parameter object

Class used here must be a 'simple' class used only for these navigations:

  • it must contain a parameterless constructor
  • it should contain only public properties with both get and set access
  • these properties should be only of types:
    • bool
    • Integral types: sbyte, short, int, long, byte, ushort, uint, ulong
    • Floating-point types: float, double
    • decimal
    • char
    • string
    • DateTime
    • Guid
    • Enumeration values

Final Note

There have been some changes to view model lifecycle made in earlier version of MvvmCross v5.x.x but as of v5.5+ the above patterns should apply.