MVVM in Xamarin Forms UWP 10

This article describes about basic architectural setup required to start working with Xamarin Forms project in MVVM design pattern. As you are aware, if you have Xamarin forms project, it should run in all (Windows, Android & ios) the platforms with minor platform specific code changes if any.

This article is targeted to run from UWP 10.

Create a new Xamarin Forms project, you will see Xamarin forms portable project library along with all the platform project templates as below:


Update the Xamarin forms nuget to latest stable version to make use of latest features.  Now, we are going to write the code in Xamarin forms portable project library which will be reused to run in all the platforms.

Create a MVVM folder structure (View, ViewModel and Model) in Xamarin forms project and add respective files appropriately.
In MVVM, INotifyPropertyChanged interface is used to notify clients, typically a binding clients, that a property value has changed.
Create a ModelBase class to implement INotifyPropertyChanged interface and use this class as base class for all the view models and models to raise property changes.

public class ModelBase: INotifyPropertyChanged
    {
         public event PropertyChangedEventHandler PropertyChanged;

         protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            var handler = this.PropertyChanged;
             if (handler != null)
            {
                 handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }

Create a BaseViewModel, to have all common functions like commands, navigation service and notify event.

public
abstract class BaseViewModel: ModelBase
    {
         protected BaseViewModel(IAppNavService navService)
         {
        }

        public abstract Task Init();

        public virtual void OnNavigatedTo()
        {
             // called when view appearing
        }

         public virtual void NavigateBack()
        {
         }
    }

public abstract class BaseViewModel<TParameter> : BaseViewModel
    {
        public BaseViewModel(IAppNavService navService) : base(navService)
        {
         }

         public override async Task Init()
        {
            await this.Init(default(TParameter));
        }

        public abstract Task Init(TParameter parameter);
    }

In MVVM, navigation service has to be implemented to navigate between the views. Create a navigation interface with below set of function declarations.

public interface IAppNavService
    {
         void RegisterMapping(Type viewModel, Type view);

        bool CanGoBack { get; }

        Task GoBack();

        Task NavigateTo<TVM>()
             where TVM : BaseViewModel;

        Task NavigateTo<TVM, TParameter>(TParameter parameter)
           where TVM : BaseViewModel;

        void RemoveLastView();

        void ClearBackStack();
    }

This service has methods “RegisterMapping”, to be used to map ViewModel to View by Binding Context.
CanGoBack, to be used to check whether the active view can be navigated back.
NavigateTo, to be used to navigate between the views, pass parameter between the views if required.
RemoveLastView, to be used to remove the last view from navigation stack, generally used to remove the loginview from stack once user is logged in.

ClearBackStack, to be used to clear the navigation stack.

Implementation of navigation service as follows.

public class AppNavService : IAppNavService
    {
         private readonly INavigation navigationRoot;

         private readonly IDictionary<Type, Type> map = new Dictionary<Type, Type>();

        public AppNavService(INavigation navigation)
         {
             this.navigationRoot = navigation;
        }

        public bool CanGoBack
        {
             get
             {
                  return this.navigationRoot.NavigationStack != null
                    && this.navigationRoot.NavigationStack.Count > 0;
             }
        }

        public async Task NavigateTo<TVM>() where TVM : BaseViewModel
        {
            await this.NavigateToView(typeof(TVM));

             if (this.navigationRoot.NavigationStack.Last().BindingContext is BaseViewModel)
            {
                await ((BaseViewModel)this.navigationRoot.NavigationStack.Last().BindingContext).Init();
             }
        }

        public async Task NavigateTo<TVM, TParameter>(TParameter parameter) where TVM : BaseViewModel
        {
            await this.NavigateToView(typeof(TVM));

             if (this.navigationRoot.NavigationStack
                 .Last().BindingContext is BaseViewModel<TParameter>)
            {
                await ((BaseViewModel<TParameter>)this.navigationRoot.NavigationStack.Last().BindingContext).Init(parameter);
             }
         }

        public void RegisterMapping(Type viewModel, Type view)
        {
             this.map.Add(viewModel, view);
        }

        public void RemoveLastView()
        {
            if (this.navigationRoot.NavigationStack.Any())
            {
                var lastView = this.navigationRoot.NavigationStack[this.navigationRoot.NavigationStack.Count - 2];
                 this.navigationRoot.RemovePage(lastView);
             }
         }

        public void ClearBackStack()
        {
            if (this.navigationRoot.NavigationStack.Count <= 1)
             {
                 return;
            }

            for (var i = 0; i < this.navigationRoot.NavigationStack.Count - 1; i++)
             {
                 this.navigationRoot.RemovePage(this.navigationRoot.NavigationStack[i]);
             }
         }

        public async Task GoBack()
        {
            if (this.CanGoBack)
            {
                await this.navigationRoot.PopAsync(true);
            }
        }

         private async Task NavigateToView(Type viewModelType)
        {
           Type viewType;

             if (!this.map.TryGetValue(viewModelType, out viewType))
            {
                throw new ArgumentException("No view found in View Mapping for " + viewModelType.FullName + ".");
            }

            var constructor = viewType.GetTypeInfo()
                .DeclaredConstructors
                .FirstOrDefault(dc => dc.GetParameters().Count() <= 0);
            var view = constructor.Invoke(null) as Page;

            var vm = ((App)Application.Current).Kernel.GetService(viewModelType);
            view.BindingContext = vm;

            await this.navigationRoot.PushAsync(view, true);
        }
    }

In MVVM, Dependency Injection or IOC is required to register and resolve the dependencies. The dependencies can be view models, Repositories or services.  In Xamarin forms project, we use NInject as one of the IOC container to register the dependencies.
Install the nugget package Portable.NInject in all the projects and create a NinjectModule similar to ServiceLocator or Bootstrap as below

public
class AppModule : NinjectModule
    {
         private readonly INavigation navigation;

         public AppModule(INavigation nav)
        {
            this.navigation = nav;
        }

        public override void Load()
        {
             // service dependencies
            this.Bind<INavigation>()
                           .ToMethod(x => this.navigation)
                           .InSingletonScope();
             this.Bind<IAppNavService>().To<AppNavService>().InSingletonScope();

            // ViewModels

             this.Bind<MainViewModel>().ToSelf();
             this.Bind<SecondViewModel>().ToSelf();
         }

        public void Map(IAppNavService navigationService)
         {
             // Register view mappings
            navigationService.RegisterMapping(
                typeof(MainViewModel),
                typeof(MainView));
            navigationService.RegisterMapping(
               typeof(SecondViewModel),
               typeof(SecondView));
        }
    }

This module has a Load method to register the dependencies. Also added a method Map to associate View and ViewModels.

Now, load the platform module and set the start page in app.xaml.cs

public App(params INinjectModule[] platformModules)
         {
             this.InitializeComponent();

             // Main page root
            var mainPage = new NavigationPage(new MainView());

            var module = new AppModule(mainPage.Navigation);
             this.Kernel = new StandardKernel(module);

             // Platform specific kernel initialization
            this.Kernel.Load(platformModules);

            var appNavigationService = this.Kernel.Get<IAppNavService>();

             // view and view model mappings
            module.Map(appNavigationService);


            // Get the MainViewModel from the IoC
            mainPage.BindingContext = this.Kernel.Get<MainViewModel>();
             this.MainPage = mainPage;
        }

As this article is targeting for UWP, add the platform Ninject module in UWP project as below. Use the same code in android, ios project templates as well if you want to run the same app.

public
class UWPAppModule : NinjectModule
    {
         public override void Load()
        {
        }
   }

Now, Open MainPage.Xaml.cs in UWP project, pass this module to xamarin forms App.

public
sealed partial class MainPage
    {
         public MainPage()
        {
             this.InitializeComponent();

             LoadApplication(new XamarinFormsIOC.App(new UWPAppModule()));
        }
    }

That’s it. We are done with MVVM architectural changes required to be implemented in real time projects.  Add below code in view model to navigate one view to another view.

private
async void Navigate()
        {
            await this.navService.NavigateTo<SecondViewModel>();
        }

If you want to use the NavigatedTo and NavigateBack events, add the virtual methods to BaseViewModel and Create a base Content Page and call these methods as below.

public
class FormsPage : ContentPage
    {
         public FormsPage()
        {
         }

         protected override void OnAppearing()
        {
             base.OnAppearing();
             NavigationPage.SetHasNavigationBar(this, false);

            var vm = this.BindingContext as BaseViewModel;
             if (vm != null)
            {
                 vm.OnNavigatedTo();
             }
         }

        /// <summary>
        /// The on back button pressed.
        /// </summary>
        /// <returns>
        /// The <see cref="bool"/>.
        /// </returns>
        protected override bool OnBackButtonPressed()
        {
            var bindingContext = this.BindingContext as BaseViewModel;
             if (bindingContext != null)
            {
                 bindingContext.NavigateBack();
             }

             return base.OnBackButtonPressed();
        }
    }

Now the view becomes as below

<
form:FormsPage xmlns="http://xamarin.com/schemas/2014/forms"
            
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                 
xmlns:form="clr-namespace:XamarinFormsIOC.Views"
            
x:Class="XamarinFormsIOC.Views.SecondView">
  <Label Text="Second View" VerticalOptions="Center" HorizontalOptions="Center" />
</form:FormsPage>

And the View Model as below.

public
class SecondViewModel : BaseViewModel<Employee>
    {
        public SecondViewModel(IAppNavService navService) : base(navService)
        {
         }

         public async override Task Init(Employee param)
         {
             // initilization
        }

         public override void OnNavigatedTo()
        {
             base.OnNavigatedTo();
        }

        public override void NavigateBack()
        {
             base.NavigateBack();
        }
    }

Download the working sample from below location
http:// nullskull.com/FileUpload/-407123783_XamarinFormsIOC.zip      

By Siva Jagan Dhulipalla   Popularity  (4039 Views)