2
\$\begingroup\$

I am playing around with Xamarin, because there is a good chance that I might have to start a project using it in a few months. What I am mostly looking at is Xamarin Forms as I am familiar with WPF and MVVM, so I am looking to apply the same principles if possible.

I usually use Caliburn.Micro but seeing as the version for Xamarin is still very unstable, I decided to try using MVVM Light Toolkit. I have no prior experience with that, so it is a bit of a struggle. For now I am only looking at the basic concepts like Bootstrapping and IoC and how all that links up with MVVM Light.

I started with something that I got from another open source Xamarin project and modified it to something that makes sense to me. I ended up with 4 classes: BootstrapperBase, NavigationService, PageRegistration, ViewModelBinder.

BootstrapperBase - this is where everything links up, it is abstract, used by extending it and making your own bootstrapper that takes care of registering your dependencies. One drawback is that this is unity only, so I am accepting suggestions on how I can abstract the container, so that others work as well:

public abstract class BootstrapperBase
    {
        protected IUnityContainer Container { get; set; }
        public void Run()
        {
            if (!ServiceLocator.IsLocationProviderSet)
            {
                Container = new UnityContainer();                
                RegisterDependencies();
                ServiceLocator.SetLocatorProvider(() => new UnityServiceLocator(Container));
            }
        }

        protected virtual void RegisterDependencies() { }

        protected void RegisterPage<TView, TViewModel>()
            where TView : Page
            where TViewModel : ViewModelBase
        {
            Container.RegisterInstance<PageRegistration>(typeof(TView).Name, new PageRegistration(typeof(TView), typeof(TViewModel)));
            Container.RegisterType<TViewModel>();
        }

        protected void RegisterPage<TView>()
            where TView : Page
        {
            Container.RegisterInstance<PageRegistration>(typeof(TView).Name, new PageRegistration(typeof(TView)));
        }
    }

NavigationService - I decided to implement my own navigation service that works with the unity container, and uses it to find and create instances of registered pages.

public class NavigationService:INavigationService
    {
        string currentPageKey;
        IUnityContainer container;

        public NavigationService(IUnityContainer container)
        {
            this.container = container;
        }

        public Page MainPage
        {
            get
            {
                return Application.Current.MainPage;
            }
        }

        public string CurrentPageKey
        {
            get { return currentPageKey; }
        }

        public void GoBack()
        {
            if (MainPage.Navigation.ModalStack.Count > 0)
            {
                MainPage.Navigation.PopModalAsync();
            }
            else
            {
                MainPage.Navigation.PopAsync();
            }
        }

        public void NavigateTo(string pageKey)
        {
            NavigateTo(pageKey, null);
        }

        public void NavigateTo(string pageKey, object parameter)
        {
            try
            {
                object[] parameters = null;
                if (parameter != null)
                {
                    parameters = new object[] { parameter };
                }

                Type pageType = ((PageRegistration)container.Resolve<PageRegistration>(pageKey)).ViewType;
                Page page = (Page)Activator.CreateInstance(pageType, parameters);
                currentPageKey = pageKey;
                MainPage.Navigation.PushAsync(page);
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
            }
        }
    }

PageRegistration - this is the class I use for storing a single page registration - contains View and ViewModel types. Pretty simple.

public class PageRegistration
    {
        public PageRegistration(Type ViewType,Type ViewModelType)
        {
            this.ViewType = ViewType;
            this.ViewModelType = ViewModelType;
        }
        public PageRegistration(Type ViewType)
        {
            this.ViewType = ViewType;
        }
        public Type ViewType { get; set; }
        public Type ViewModelType { get; set; }
    }

And last but not least is the ViewModelBinder - this is the class that takes care of hooking up the views to the viewmodel. It actually contains extension methods for BindableObject, which is the class that all Pages in Xamarin inherit from. Again uses the IoC container to locate the viewmodel.

public static class ViewModelBinder
    {
        public static void BindViewModel(this BindableObject bindable)
        {
            bindable.BindingContext = GetViewModel(bindable);
        }

        public static ViewModelBase GetViewModel(this BindableObject bindable)
        {
            Type viewModelType = ServiceLocator.Current.GetInstance<PageRegistration>(bindable.GetType().Name).ViewModelType;
            return (ViewModelBase)ServiceLocator.Current.GetInstance(viewModelType);
        }
    }

As you may have noticed, I have used ServiceLocator at some places. That is because I just couldn't think of a way to get the UnityContainer reference to the ViewModelBinder. If you have any ideas on how I can do that in a better way I am all open for ideas.

This is the App constructor. It's the first thing that runs. Takes care of running the Bootstrapper and selecting a MainPage.

public App()
        {
            Bootstrapper.Instance.Run();
            MainPage = GetMainPage();
        }

        public static Page GetMainPage()
        {
            return new NavigationPage(new MainView());
        }

Here is the Bootstrapper implementation that I use:

public class Bootstrapper:BootstrapperBase
    {
        protected override void RegisterDependencies()
        {
            Container.RegisterType<INavigationService, NavigationService>(new ContainerControlledLifetimeManager());
            RegisterPages();
        }

        private void RegisterPages()
        {
            RegisterPage<MainView, MainViewModel>();
            RegisterPage<UserView, UserViewModel>();
        }

        private static Bootstrapper instance;
        public static Bootstrapper Instance
        {
            get { return instance??(instance=new Bootstrapper()); }
        }
    }

One thing that I feel like I should explain about the NavigationService is that it uses the name of registrations in the IoC container in order to locate the view/viewmodel that it is looking for. For example when you do this:

RegisterPage<MainView, MainViewModel>();

Two entries get registered in the container: one for the PageRegistration that says this ViewModel should link up with this View, with a name that corresponds to the View type and another one that registers the ViewModel as a type so that it can later be built up by the IoC container.

So in this case the container will have the following entries added:

  1. InstanceRegistration: Name = "MainView", Instance = new PageRegistration(typeof(MainView), typeof(MainViewModel))

  2. TypeRegistration: RegisteredType = typeof(MainViewModel)

Later when it needs to navigate to a page and is passed the name "UserView" for example, it looks in the container for a registration with name UserView, gets the PageRegistration instance that is registered under that name and then gets the type of the page that it needs to open. After that it simply instantiates it using reflection and pushes it to the screen.

There is something very rotten in this concept but I cannot put my finger on it. Is this a good way to do it? I am pretty sure it isn't. It works but it ain't pretty. Any ideas on how I can do it better?

Usually when I write something like this, monstrosities are born, ugly ones, so I decided to come here and ask for your opinions.

\$\endgroup\$

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.