【问题标题】:crash when navigating from a viewmodel to another viewmodel in mvvmcross从视图模型导航到 mvvmcross 中的另一个视图模型时崩溃
【发布时间】:2018-06-23 10:47:53
【问题描述】:

我想使用 mvvmcross 中的视图模型从一个屏幕移动到下一个屏幕,并将一些模型传递给下一个视图模型。但我遇到了以下崩溃:

MvvmCross.Platform.Exceptions.MvxException:无法从定位器 MvxDefaultViewModelLocator 构造和初始化 iManage.ViewModels.LoginViewModel 类型的 ViewModel - 检查 InnerException 以获取更多信息 ---> MvvmCross.Platform.Exceptions.MvxException:创建类型的 viewModel 时出现问题LoginViewModel ---> MvvmCross.Platform.Exceptions.MvxIoCResolveException:创建 iManage.ViewModels.LoginViewModel 时无法解析 SchoolModel 类型参数项的参数 在 MvvmCross.Platform.IoC.MvxSimpleIoCContainer.GetIoCParameterValues(System.Type 类型,System.Reflection.ConstructorInfo firstConstructor)[0x00066] 在 :0 在 MvvmCross.Platform.IoC.MvxSimpleIoCContainer.IoCConstruct (System.Type 类型) [0x0002c] 在 :0 在 MvvmCross.Platform.Mvx.IocConstruct (System.Type t) [0x00006] 在 :0 在 MvvmCross.Core.ViewModels.MvxDefaultViewModelLocator.Load (System.Type viewModelType, MvvmCross.Core.ViewModels.IMvxBundle parameterValues, MvvmCross.Core.ViewModels.IMvxBundle savedState) [0x00000] in :0 --- 内部异常堆栈跟踪结束 --- 在 MvvmCross.Core.ViewModels.MvxDefaultViewModelLocator.Load (System.Type viewModelType, MvvmCross.Core.ViewModels.IMvxBundle parameterValues, MvvmCross.Core.ViewModels.IMvxBundle savedState) [0x00029] in :0 在 MvvmCross.Core.ViewModels.MvxViewModelLoader.LoadViewModel (MvvmCross.Core.ViewModels.MvxViewModelRequest 请求,MvvmCross.Core.ViewModels.IMvxBundle savedState)[0x00035] 在:0 --- 内部异常堆栈跟踪结束 --- 在 MvvmCross.Core.ViewModels.MvxViewModelLoader.LoadViewModel (MvvmCross.Core.ViewModels.MvxViewModelRequest 请求,MvvmCross.Core.ViewModels.IMvxBundle savedState)[0x00068] 在:0 在 MvvmCross.iOS.Views.MvxViewControllerExtensionMethods.LoadViewModel (MvvmCross.iOS.Views.IMvxIosView iosView) [0x0005f] 在 :0 在 MvvmCross.Core.Views.MvxViewExtensionMethods.OnViewCreate (MvvmCross.Core.Views.IMvxView 视图,System.Func`1[TResult] viewModelLoader) [0x00012] in :0 在 MvvmCross.iOS.Views.MvxViewControllerExtensionMethods.OnViewCreate (MvvmCross.iOS.Views.IMvxIosView iosView) [0x00001] 在 :0 在 中的 MvvmCross.iOS.Views.MvxViewControllerAdapter.HandleViewDidLoadCalled (System.Object sender, System.EventArgs e) [0x00007]:0 at at (wrapper delegate-invoke) :invoke_void_object_EventArgs (object,System.EventArgs) 在 MvvmCross.Platform.Core.MvxDelegateExtensionMethods.Raise (System.EventHandler eventHandler, System.Object sender) [0x00003] in :0 在 MvvmCross.Platform.iOS.Views.MvxEventSourceViewController.ViewDidLoad () [0x00006] 在 :0 在 MvvmCross.iOS.Views.MvxViewController.ViewDidLoad () [0x00001] 在 :0 在 iManage.iOS.Views.LoginView.ViewDidLoad () [0x00001] 在 /Users/pankajsachdeva/Projects/iManage/iOS/Views/LoginView.cs:18 at at (wrapper managed-to-native) UIKit.UIApplication:UIApplicationMain (int,string[],intptr,intptr) 在 /Users/builder/data/lanes/5665/f70a1348/source/xamarin-macios/src/UIKit 中的 UIKit.UIApplication.Main(System.String[] args,System.IntPtr 主体,System.IntPtr 委托)[0x00005] /UIApplication.cs:79 在 /Users/builder/data/lanes/5665/f70a1348/source/xamarin-macios/src/UIKit 中的 UIKit.UIApplication.Main (System.String [] args, System.String principalClassName, System.String delegateClassName) [0x00038] /UIApplication.cs:63 在 iManage.iOS.Application.Main (System.String[] args) [0x00001] 在 /Users/pankajsachdeva/Projects/iManage/iOS/Main.cs:17

来自 ViewModel 代码:

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));
    }
}

到 ViewModel 代码:

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));
    }
}

编辑1: 在我的 ViewModel 中修改了以下内容:

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

将下一个 ViewModel 声明更改如下:

public class LoginViewModel : MvxViewModel<SchoolModel>

当我尝试显示下一个视图模型时它仍然崩溃,并出现以下错误: 对象引用未设置为对象的实例。
Edit2: 完全错误:

System.NullReferenceException:对象引用未设置为对象的实例 在 iManage.ViewModels.SchoolSelectionViewModel+d__19.MoveNext () [0x0000f] 在 /Users/pankajsachdeva/Projects/iManage/iManage/ViewModels/SchoolSelectionViewModel.cs:67 在 System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () [0x0000c] 在 /Library/Frameworks/Xamarin.iOS.framework/Versions/11.6.1.3/src/mono/mcs/class/referencesource/mscorlib/system/runtime/exceptionservices /exceptionservicescommon.cs:152 在 System.Runtime.CompilerServices.AsyncMethodBuilderCore+c.b__6_0(System.Object 状态)[0x00000] 在 /Library/Frameworks/Xamarin.iOS.framework/Versions/11.6.1.3/src/mono/mcs/class/referencesource/ mscorlib/system/runtime/compilerservices/AsyncMethodBuilder.cs:1018 在 UIKit.UIKitSynchronizationContext+c__AnonStorey0.m__0 () [0x00000] 在 /Users/builder/data/lanes/5665/f70a1348/source/xamarin-macios/src/UIKit/UIKitSynchronizationContext.cs:24 在 /Users/builder/data/lanes/5665/f70a1348/source/xamarin-macios/src/Foundation/NSAction.cs:163 中的 Foundation.NSAsyncActionDispatcher.Apply () [0x00000] at at (wrapper managed-to-native) UIKit.UIApplication:UIApplicationMain (int,string[],intptr,intptr) 在 /Users/builder/data/lanes/5665/f70a1348/source/xamarin-macios/src/UIKit 中的 UIKit.UIApplication.Main(System.String[] args,System.IntPtr 主体,System.IntPtr 委托)[0x00005] /UIApplication.cs:79 在 /Users/builder/data/lanes/5665/f70a1348/source/xamarin-macios/src/UIKit 中的 UIKit.UIApplication.Main (System.String [] args, System.String principalClassName, System.String delegateClassName) [0x00038] /UIApplication.cs:63 在 iManage.iOS.Application.Main (System.String[] args) [0x00001] 在 /Users/pankajsachdeva/Projects/iManage/iOS/Main.cs:17

【问题讨论】:

  • 这就是为什么 ViewModel 导航没有任何意义哈哈。
  • “对象引用未设置为对象的实例”从何而来?你能附上完整的异常堆栈跟踪吗?你用的是什么版本的 MvvmCross?
  • 请检查我的编辑
  • SchoolSelectionViewModel.cs 的第 67 行是什么?您似乎可以在此处找到空引用
  • 等待_navigationService.Navigate(item); item 是 SchoolModel 类型的对象,它有数据并且不为空

标签: c# xamarin mvvm xamarin.ios mvvmcross


【解决方案1】:

MvvmCross.Platform.Exceptions.MvxIoCResolveException:在 MvvmCross.Platform.IoC.MvxSimpleIoCContainer.GetIoCParameterValues(System.Type 类型)创建 iManage.ViewModels.LoginViewModel 时,无法解析 SchoolModel 类型的参数项的参数, System.Reflection.ConstructorInfo firstConstructor)

问题在于LoginViewModel 的构造函数中使用的参数SchoolModel 的类型。尝试构造 LoginViewModel 时,MvxSimpleIoCContainer 无法解析类型。

所以我要解决两个问题

  1. 通过查看 MvvmCross 如何构建视图模型,您为什么会遇到此异常。
  2. 如何使用 navigation service introduced in MvvmCross 5 和具有 MvvmCross 5 生命周期的传统 ShowViewModel 在视图模型之间传递参数。

在 MvvmCross 中查看模型 IoC 使用情况

在MvvmCross view models are constructed via IoC。这意味着视图模型类的公共构造函数的参数需要向 IoC 容器注册,以便容器构造类。

用作构造参数的类型也很重要,因为默认情况下 MvvmCross 使用 Service Locator pattern 仅支持针对实现类型注册的接口类型。注册实现类型Foo 和接口类型IFoo 的示例。

Mvx.ConstructAndRegisterSingleton<IFoo, Foo>();

由于 MvvmCross 对视图模型使用 构造函数注入,您不能使用视图模型类的公共构造函数将自己的参数传递给视图模型类实例。构造函数参数必须能够来自 IoC 容器。


MvvmCross 5 在视图模型之间传递参数(导航服务)

在MvvmCross 5 the Navigation Service 中引入了作为在 MvvmCross 中编写导航逻辑的首选方式。导航服务还提供了一种在视图模型之间传递参数的方法,以避免通过视图模型构造函数进行传递。 MvvmCross documentation 中有一个很好的例子,展示了如何传递参数。这是摘录

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;
    }
}

快速解释。从NextViewModel 开始,视图模型导航到,您需要在继承的MvxViewModel 基类泛型中指定要传递的参数的类型,然后覆盖Prepare 以传递参数。在navigate 方法的调用类MyViewModel 中,您包括导航到的视图模型的类型以及您传递的参数的类型。然后将参数作为navigate 方法的第一个参数传递。


在视图模型之间传递参数(ShowViewModel)

在您的示例代码中,您仍在使用旧的ShowViewModel 模式进行导航,因此我将介绍如何使用ShowViewModel 方法传递参数。可以在 here for the complex parameter object 和 here for the simple parameter object 找到用于传递 ShowViewModel 参数的 MvvmCross 文档。

复杂的参数对象

对于复杂的参数对象,MvvmCross 要求您安装 MvvmCross Json 插件。这种方法会对传递的参数进行序列化和反序列化

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;
    }
}

如果您不能让您的LoginViewModel 继承MvxViewModel&lt;TParameter&gt;,您可能还需要使用IMvxViewModel&lt;TParameter&gt; 合约实现自定义基类。

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);
}

简单的参数对象

对于不需要序列化的基本类型,可以使用这种方法,但会损失类型安全参数。

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;
    }
}

注意与简单参数对象一起使用的类型规则

此处使用的类必须是仅用于这些导航的“简单”类:

  • 它必须包含一个无参数的构造函数
  • 它应该只包含具有get 和set 访问权限的公共属性
  • 这些属性只能是以下类型:
    • bool
    • 积分类型:sbyte、short、int、long、byte、ushort、uint、ulong
    • 浮点类型:float、double
    • decimal
    • char
    • string
    • DateTime
    • Guid
    • 枚举值

最后说明

在早期版本的 MvvmCross v5.x.x 中对视图模型生命周期进行了一些更改,但从 v5.5+ 开始,上述模式应该适用。

【讨论】:

  • 感谢您的回答,我按照您的建议进行了更改,并使用 Navigate 进行导航,但应用程序崩溃了。请检查我的 Edit1。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-09
  • 1970-01-01
  • 1970-01-01
  • 2013-07-02
相关资源
最近更新 更多