Forum Xamarin.Forms

Announcement:

The Xamarin Forums have officially moved to the new Microsoft Q&A experience. Microsoft Q&A is the home for technical questions and answers at across all products at Microsoft now including Xamarin!

To create new threads and ask questions head over to Microsoft Q&A for .NET and get involved today.

Navigation in MVVM

gogolongogolon Member ✭✭
edited January 9 in Xamarin.Forms

Hello,
I want to navigate between view models in my MVVM app. The app is small and I'd rather avoid using a special framework. I came up with a following solution:

    //namespace ViewModels.Services
    interface INavigationService
    {
        Task Navigate<T>(T viewModel);
    }
    //namespace ViewModels.Services
    class NavigationService : INavigationService
    {
        public async Task Navigate<T>(T viewModel)
        {
            ContentPage page;

            if (viewModel is SomeViewModel)
                page = new SomePage();
            else if (viewModel is SomeViewModel3)
                page = SomePage2();
            else if (viewModel is SomeViewModel3)
                page = SomePage3();
            //else if (...) {...}
            else
                throw new InvalidNavigationException();

            page.BindingContext = viewModel;
            await App.Current.MainPage.Navigation.PushAsync(page);
        }
    }
            //App.cs, constructor
            DependencyService.Register<ViewModels.Services.INavigationService, ViewModels.Services.NavigationService>();
//ViewModel
var navigationService = DependencyService.Get<Services.INavigationService>();
await navigationService.Navigate(new SomeViewModel());

is it an acceptable way of achieving that? If not, what else can I do?

Tagged:

Best Answer

  • JohnHardmanJohnHardman GBUniversity admin
    Accepted Answer

    @gogolon said:
    Is that what you meant? Also, could you please explain to me why do we even make it a service and use DependencyService if the implementation is the same for both Android and iOS? Wouldn't it be easier to just create a static class?

    Yes, that's the sort of thing. Using Activator.CreateInstance is one way of doing it. Lots of people do it that way. Another is to store a Func in the dictionary where the Func does the creation. Either will work.

    It doesn't need to be a DependencyService if you are only calling this from portable code (which typically will be the case). Referring to a Navigation Service does not mean that the Navigation Service is a DependencyService, just that it's a chunk of code that provides functionality to other code.

    A static class, or a non-static class with a private constructor and a static Instance property (or GetInstance method), will certainly work.

Answers

  • Angelru9Angelru9 ESMember ✭✭✭

    Here's a good example of custom navigation:
    https://github.com/jsuarezruiz/xamarin-forms-netflix-sample

  • gogolongogolon Member ✭✭

    It uses Autofac. As I said, I would rather do that without additional packages/frameworks. Thanks for posting it though, it gave me an idea of how the service should look like.

  • JohnHardmanJohnHardman GBUniversity admin

    @gogolon said:

        //namespace ViewModels.Services
        class NavigationService : INavigationService
        {
            public async Task Navigate<T>(T viewModel)
            {
                ContentPage page;
    
                if (viewModel is SomeViewModel)
                    page = new SomePage();
                else if (viewModel is SomeViewModel3)
                    page = SomePage2();
                else if (viewModel is SomeViewModel3)
                    page = SomePage3();
              //else if (...) {...}
                else
                    throw new InvalidNavigationException();
    
                page.BindingContext = viewModel;
                await App.Current.MainPage.Navigation.PushAsync(page);
            }
        }
    

    By putting that in the ViewModels.Services namespace, you are giving your ViewModel layer knowledge of the View layer.
    It would be better to have your NavigationService maintain a dictionary of View Model types and Funcs that create an instance of the corresponding Page type for that View Model. That dictionary is initially empty. When your app starts, have the portable App class (derived from Xamarin.Forms.Application) populate that dictionary, adding each View Model and related Func in turn. That way, your NavigationService can still function as expected, but being generic, rather than having knowledge of the View layer.

  • gogolongogolon Member ✭✭
    edited January 14

    Thank you for replying @JohnHardman. This is my attempt of creating the service using your approach:

        //Namespace Services.Navigation
        class NavigationService : INavigationService
        {
            protected INavigation Navigation => App.Current.MainPage.Navigation;
            public static Dictionary<Type, Type> Mappings;
    
            protected Type GetPageTypeForViewModel(Type viewModelType)
            {
                if (!Mappings.ContainsKey(viewModelType))
                {
                    throw new KeyNotFoundException($"No map for ${viewModelType} was found on navigation mappings");
                }
    
                return Mappings[viewModelType];
            }
    
            protected Page CreateAndBindPage<T>(T viewModel)
            {
                Type viewModelType = viewModel.GetType();
                Type pageType = GetPageTypeForViewModel(viewModelType);
    
                if (pageType == null)
                {
                    throw new Exception($"Mapping type for {viewModelType} is not a page");
                }
    
                Page page = Activator.CreateInstance(pageType) as Page;
                page.BindingContext = viewModel;
    
                return page;
            }
    
            public async Task NavigateAsync<T>(T viewModel)
            {
                var stack = Navigation.NavigationStack;
                if (stack[stack.Count - 1].BindingContext is T)
                    return;
    
                var page = CreateAndBindPage(viewModel);
                await Navigation.PushAsync(page);
            }
    
            public async Task PopAsync()
            {
                await Navigation.PopAsync();
            }
        }
    
        //App.cs
            public App()
            {
                var mappings = new Dictionary<Type, Type>();
                mappings.Add(typeof(SomeViewModel1), typeof(SomeView1));
                mappings.Add(typeof(SomeViewModel2), typeof(SomeView2));
                mappings.Add(typeof(SomeViewModel3), typeof(SomeView3));
                Services.Navigation.NavigationService.Mappings = mappings;
    
                DependencyService.Register<Services.Navigation.INavigationService, Services.Navigation.NavigationService>();
            }
    

    Is that what you meant? Also, could you please explain to me why do we even make it a service and use DependencyService if the implementation is the same for both Android and iOS? Wouldn't it be easier to just create a static class?

  • JohnHardmanJohnHardman GBUniversity admin
    Accepted Answer

    @gogolon said:
    Is that what you meant? Also, could you please explain to me why do we even make it a service and use DependencyService if the implementation is the same for both Android and iOS? Wouldn't it be easier to just create a static class?

    Yes, that's the sort of thing. Using Activator.CreateInstance is one way of doing it. Lots of people do it that way. Another is to store a Func in the dictionary where the Func does the creation. Either will work.

    It doesn't need to be a DependencyService if you are only calling this from portable code (which typically will be the case). Referring to a Navigation Service does not mean that the Navigation Service is a DependencyService, just that it's a chunk of code that provides functionality to other code.

    A static class, or a non-static class with a private constructor and a static Instance property (or GetInstance method), will certainly work.

Sign In or Register to comment.