vbn
vbn

Reputation: 799

mvvmcross: NavigationService.Navigate throws an MvxException "Unable to find incoming mvxviewmodelrequest"

In my WP8 app, I have MainView referencing MainViewModel. MainView is a menu where users can navigate to other views to do some tasks. Navigating from MainView works perfectly as I use ShowViewModel. However, navigating from other views when user completes a task, back to MainView using NavigationService.Navigate(URI) throws an exception "Unable to find incoming mvxviewmodelrequest".

To avoid this exception, I have construct the URI like below

var req = "{\"ViewModelType\":\"MyApp.Core.ViewModels.MainViewModel, MyApp.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null\",\"ClearTop\":\"true\",\"ParameterValues\":null,\"RequestedBy\":null}";
NavigationService.Navigate(new Uri("/MainView.xaml?ApplicationUrl=" + Uri.EscapeDataString(req), UriKind.Relative));

Does anyone have a better way to use NavigationService.Navigate?

Upvotes: 0

Views: 1057

Answers (1)

Stuart
Stuart

Reputation: 66882

Most navigations in the MvvmCross samples are initiated by either MvxAppStart objects or by MvxViewModels. Both of these classes inherit from MvxNavigatingObject and use the ShowViewModel methods exposed there - see https://github.com/MvvmCross/MvvmCross/blob/v3.1/Cirrious/Cirrious.MvvmCross/ViewModels/MvxNavigatingObject.cs

From MvxNavigatingObject, you can see that MvvmCross routes the navigation call to the IMvxViewDispatcher which in WindowsPhone is a very thin object - all it does is marshall all calls to the UI thread and to pass them on to the IMvxViewPresenter - see https://github.com/MvvmCross/MvvmCross/blob/v3.1/Cirrious/Cirrious.MvvmCross.WindowsPhone/Views/MvxPhoneViewDispatcher.cs

The presenter is an object created in Setup - and the default implementation uses an IMvxPhoneViewModelRequestTranslator to convert the navigation call into a uri-based navigation - see https://github.com/MvvmCross/MvvmCross/blob/v3.1/Cirrious/Cirrious.MvvmCross.WindowsPhone/Views/MvxPhoneViewPresenter.cs

Silverlight/WindowsPhone then uses this uri for navigation, creates the necessary Xaml page, and then calls OnNavigatedTo on this page. As part of the base.OnNavigatedTo(); handing in MvxPhonePage, MvvmCross then calls the OnViewCreated extension method. This method checks if there is already a ViewModel - if there isn't one then it attempts to locate one using the information in the uri - see https://github.com/MvvmCross/MvvmCross/blob/v3.1/Cirrious/Cirrious.MvvmCross.WindowsPhone/Views/MvxPhoneExtensionMethods.cs


With this explanation in mind, if any app ever wants to initiate an MvvmCross navigation from a class which doesn't already inherit from MvxNavigatingObject - e.g. from some Service or from some other class, then there are several options:

  1. You can provide a shim object to do the navigation - e.g.:

     public class MyNavigator : MvxNavigatingObject {
          public void DoIt() {
              ShowViewModel<MyViewModel>();
          }
     }
    
     // used as:
     var m = new MyNavigator();
     m.DoIt();
    
  2. You can instead use IoC to locate the IMvxViewDispatcher or IMvxViewPresenter and can call their Show methods directly

     var request = MvxViewModelRequest<MyViewModel>.GetDefaultRequest();
     var presenter = Mvx.Resolve<IMvxViewPresenter>();
     presenter.Show(request);
    
  3. You can write manual code which mimics what the IMvxViewPresenter does - exactly as you have in your code - although it might be "safer" to use the IMvxPhoneViewModelRequestTranslator.cs to assist with generate the url - see https://github.com/MvvmCross/MvvmCross/blob/v3.1/Cirrious/Cirrious.MvvmCross.WindowsPhone/Views/IMvxPhoneViewModelRequestTranslator.cs

     var request = MvxViewModelRequest<MyViewModel>.GetDefaultRequest();
     var translator = Mvx.Resolve<IMvxPhoneViewModelRequestTranslator>();
     var uri = translator.GetXamlUriFor(request);
    

One other option that Views always have is that they don't have to use the standard MvvmCross navigation and ViewModel location. In WindowsPhone, your code can easily set the ViewModel directly using your own logic like:

    protected override void OnNavigatedTo(NavigationEventArgs e)
    {
        if (ViewModel == null) {
           ViewModel = // something I locate
        }

        // if you are doing your own logic then `base.OnNavigatedTo` isn't really needed in winphone
        // but I always call it anyway
        base.OnNavigatedTo(e);
    }

Alternatively in WindowsPhone, you can even replace MvxPhonePage with a different base class that uses it's own logic for viewmodel location. This is easy to do in WindowsPhone as all Xaml pages have built-in data-binding support.

Upvotes: 6

Related Questions