InternetException

About coding and whatnot.

Detecting software keyboard events in Xamarin Android

clock February 4, 2017 00:31 by author n.podbielski

Introduction

It is quite strange that you can't easily detect if software keyboard is visible or not on Android. Because of that, it is also complicated thing to do in Xamarin.

One time, when I was working on Xamarin application, I had a problem with software keyboard overlapping text box it supposed to edit. If there would be some kind of system event that could be used to detect keyboard popup, this would be easy fix. But there is not. After some googling, I found this blog post, which pointed me in the right direction. Because I wanted reusable service for Dependency Injection inside of view models I decided to that a little differently.

Biggest drawback of this service is that it is kind of hack. It do not directly bind to android software keyboard. Instead of that, it reads the changes of global layout, which happens whenever software keyboard popups (because screen space available for application is about half of screen then). When layout changes, we can safely check if keyboard is actually visible, which is (at least that) easy to test.

 

Solution

Let's create simple Xamarin Android application, with single view like below.

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="KeyboardService.View.MainPageView">
  <Grid HorizontalOptions="FillAndExpand" Padding="0" ColumnSpacing="0"
        x:Name="Grid" VerticalOptions="FillAndExpand" RowSpacing="0">
    <Grid.RowDefinitions>
      <RowDefinition Height="Auto" />
      <RowDefinition Height="*" />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="Auto" />
      <ColumnDefinition Width="*" />
    </Grid.ColumnDefinitions>
    <StackLayout Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"
                 HorizontalOptions="FillAndExpand">
      <Label Text="Keyboard service app" FontSize="40" HorizontalOptions="Center" HorizontalTextAlignment="Center" />
    </StackLayout>
    <Grid Grid.Row="1" Grid.Column="1" HorizontalOptions="Fill" VerticalOptions="FillAndExpand"
          BackgroundColor="Gray" x:Name="Content">
      <Grid.RowDefinitions>
        <RowDefinition Height="600" />
        <RowDefinition/>
      </Grid.RowDefinitions>
      <StackLayout Grid.Row="0" VerticalOptions="End"
                   HorizontalOptions="FillAndExpand">
        <Label Text="{Binding Event}" FontSize="40" />
      </StackLayout>
      <Entry Grid.Row="1" Grid.Column="0" FontSize="40"
                              BackgroundColor="Gray" Text="Entry" />
    </Grid>
  </Grid>
</ContentPage>

It is just a simple view with single text box and single label. This is just enough to test keyboard events. Entry control raises keyboard and label should change text when keyboard service event will be invoked (on keyboard show or hide).

To implement KeyboardService we need to implement ViewTreeObserver.IOnGlobalLayoutListener first. It is Android interface so it requires Java.Lang.Object type, which can be inherited by any class. It do not necessarily have to be Activity as I explained in this post and how it is done in blog post from introduction.

internal class GlobalLayoutListener : Object, ViewTreeObserver.IOnGlobalLayoutListener
{
    private static InputMethodManager _inputManager;

    private static void ObtainInputManager()
    {
        _inputManager = (InputMethodManager)TinyIoCContainer.Current.Resolve<Activity>()
            .GetSystemService(Context.InputMethodService);
    }

    public void OnGlobalLayout()
    {
        if (_inputManager.Handle == IntPtr.Zero)
        {
            ObtainInputManager();
        }
        //Keyboard service events
    }
}

To ease obtaining of references wherever they needed, we can use TinyIoC library inside the project for Dependency Injection. This way we can easily use MainActivity class as Activity inside GlobalLayoutListener, to get Android InputMethodManager object, which can be used to test if software keyboard is visible or not.

Sometimes reference to InputMethodManager inside _inputManager value is no longer valid. It mainly happens whenever OS sends application into background or otherwise it is no longer visible, hence the if (_inputManager.Handle == IntPtr.Zero) condition. Whenever handle to Java object is invalid, new reference to InputMethodManager is created.

Testing if keyboard is visible, can be done by checking value of IsAcceptingText property. If it is true - software keyboard accepts text input and it is visible.

if (_inputManager.IsAcceptingText)
{
    //Invoke Show event
}
else
{
    //Invoke Hide event
}

To implement software keyboards events and invoke them from the above code, we need to create service interface first with those events.

public interface ISoftwareKeyboardService
{
    event SoftwareKeyboardEventHandler Hide;

    event SoftwareKeyboardEventHandler Show;
}

We need only two events for hiding and showing of software keyboard. Events delegate and its event arguments type are very simple classes.

public delegate void SoftwareKeyboardEventHandler(object sender, SoftwareKeyboardEventArgs args);

public class SoftwareKeyboardEventArgs : EventArgs
{
    public SoftwareKeyboardEventArgs(bool isVisible)
    {
        IsVisible = isVisible;
    }

    public bool IsVisible { get; private set; }
}

Just a simple IsVisible property to check if keyboard is visible or not.

Generally, it is good idea to write as much code in shared project as possible, so we should split implementation of service interface into two classes: SoftwareKeyboardServiceBase inside shared project (or PCL) and SoftwareKeyboardService in Android platform project. We can't do both in the same type because GlobalLayoutListener is Android platform class and cannot be used inside PCL. Also if PCL class with service interface is placed inside PCL, it possibly can be implemented on different platforms (if necessary, of course). First we should take care of platform independent class.

public abstract class SoftwareKeyboardServiceBase : ISoftwareKeyboardService
{
    public virtual event SoftwareKeyboardEventHandler Hide;

    public virtual event SoftwareKeyboardEventHandler Show;

    public void InvokeKeyboardHide(SoftwareKeyboardEventArgs args)
    {
        var handler = Hide;
        handler?.Invoke(this, args);
    }

    public void InvokeKeyboardShow(SoftwareKeyboardEventArgs args)
    {
        var handler = Show;
        handler?.Invoke(this, args);
    }
}

It is just implementation of ISoftwareKeyboardService interface, plus methods for invoking keyboard events from outside of service itself. They can be easily used from GlobalLayoutListener. To do that, we have to create instance of listener first and register it inside Android activity. It can be done at application start, but if application would never use those events and/or service at all, it would be waste of memory. So it is better idea, to create listener and register it, only if service and its events are used. Since events accessors can be customized in C#, we can do that in those. Platform implementation of SoftwareKeyboardService will then look like this.

public class SoftwareKeyboardService : SoftwareKeyboardServiceBase
{
    private readonly MainActivity _activity;
    private GlobalLayoutListener _globalLayoutListener;

    public SoftwareKeyboardService(Activity activity)
    {
        _activity = activity;
    }

    public override event SoftwareKeyboardEventHandler Hide
    {
        add
        {
            base.Hide += value;
            CheckListener();
        }
        remove { base.Hide -= value; }
    }

    public override event SoftwareKeyboardEventHandler Show
    {
        add
        {
            base.Show += value;
            CheckListener();
        }
        remove { base.Show -= value; }
    }

    private void CheckListener()
    {
        if (_globalLayoutListener == null)
        {
            _globalLayoutListener = new GlobalLayoutListener(this);
            _activity.Window.DecorView.ViewTreeObserver.AddOnGlobalLayoutListener(_globalLayoutListener);
        }
    }
}

In add accessors of both Hide and Show events CheckListener method is executed. It takes care of creating instance of GlobalLayoutListener and registering it in ViewTreeObserver obtained from activity, first time event handler is added to one of service events. This is nice. 

Activity will be resolved from IoC, if service will be resolved from IoC too. If not, it has to be injected some other way. Of course, it is also possible to tap into ViewTreeObserver some other way and since, this object is available from any Android control, it is totally possible, but getting it from main Android window is really convenient.

As you can see, there is new custom constructor of GlobalLayoutListener type with service instance as a parameter.

public GlobalLayoutListener(SoftwareKeyboardService softwareKeyboardService)
{
    _softwareKeyboardService = softwareKeyboardService;
    ObtainInputManager();
}

Saving service instance allow us, to use it whenever global layout changes to invoke appropriate event inside OnGlobalLayout method.

if (_inputManager.IsAcceptingText)
{
    _softwareKeyboardService.InvokeKeyboardShow(new SoftwareKeyboardEventArgs(true));
}
else
{
    _softwareKeyboardService.InvokeKeyboardHide(new SoftwareKeyboardEventArgs(false));
}

Last thing is, to create view model for our main page with keyboard service instance. Most important parts of this class are below.

public MainPageViewModel()
{
    var keyboardService = TinyIoCContainer.Current.Resolve<ISoftwareKeyboardService>();
    keyboardService.Hide += _keyboardService_Hide;
    keyboardService.Show += _keyboardService_Show;
}

private void _keyboardService_Show(object sender, SoftwareKeyboardEventArgs args)
{
    Event = "Show event handler invoked";
}

private void _keyboardService_Hide(object sender, SoftwareKeyboardEventArgs args)
{
    Event = "Hide event handler invoked";
}

public string Event
{
    get { return _event; }
    set
    {
        _event = value;
        OnPropertyChanged();
    }
}

Keyboard service instance is resolved from IoC during view model constructor. Then events handlers are attached to events and in the background GlobalLayoutListener is created and added to ViewTreeObserver. Whenever keyboard shows or hides, value of label should change.

This is enough to make our service work. After starting our simple application we can check if this works.

As you can see label value changes on software popup, which means service works fine.

Summary

This is all what is needed to make reusable Xamarin Android software keyboard service.

Code from article is available for download here: KeyboardService-master.zip (43.90 kb) or from GitHub.



Xamarin Android Services Pattern on GPS example

clock January 30, 2017 05:17 by author n.podbielski

Introduction

In Xamarin if you want to use many Android native service/mechanism you have to implement it in Activity class. Or at least that is how Xamarin documentation wants you to do it.

For example if you want to use GPS to acquire current device location. According to official documentation you have to implement Android Activity similar to something below.

public class MainActivity : FormsApplicationActivity, ILocationListener
{
    private LocationManager _locMgr;
    private string _provider = LocationManager.GpsProvider;
    private object _locationProvider;

    public void OnLocationChanged(Location location)
    {
    }

    public void OnProviderDisabled(string provider)
    {
            
    }

    public void OnProviderEnabled(string provider)
    {
            
    }

    public void OnStatusChanged(string provider, Availability status, Bundle extras)
    {
            
    }

    protected override void OnCreate(Bundle bundle)
    {
        base.OnCreate(bundle);

        Xamarin.Forms.Forms.Init(this, bundle);
        LoadApplication(new App());
        _locMgr = GetSystemService(LocationService) as LocationManager;
        var locationCriteria = new Criteria
        {
            Accuracy = Accuracy.Coarse,
            PowerRequirement = Power.Medium
        };
        if (_locMgr != null)
        {
            _locationProvider = _locMgr.GetBestProvider(locationCriteria, true);
            if (_locationProvider != null)
            {
                _locMgr.RequestLocationUpdates(_provider, 2000, 1, this);
            }
            else
            {
                throw new Exception();
            }
        }
    }

    protected override void OnPause()
    {
        base.OnPause();
        _locMgr.RemoveUpdates(this);
    }

    protected override void OnResume()
    {
        base.OnResume();
        _locMgr.RequestLocationUpdates(_provider, 2000, 1, this);
    }
}

Few words of explanation. ILocationListener is Android interface that is used by OS to inform application about changed GPS location of a device. In most simple example it is implemented inside Activity.
What is problem with above? When this kind of logic extends Activity class, it makes really hard to share GPS functionality across your applications. Moreover there are other Android system mechanism that might be recommended to implement them this way (like GSM signal, network access, global layout. etc.). If done this way it would make Activity class really bloated and hard to maintain. Also this is problematic to use that kind of service implementation in view models. Unless you code even more features into MainActivity class (new methods, events and static access to MainActivity instance i.e.), which is not what we want to do.

Another bad idea is to use native Android views like this is done in Xamarin GPS documentation. It is funny considering that they advertise its own framework with possibility of sharing code across platforms and then ask you to use native views to achieve something such basic on mobile like GPS location Smile. Yes it would be possible to update Xamarin view with new location instead of native view, but I honestly do not see the point unless you are really in hurry and application have only one view. In more complicated applications using of GPS in another view would force you to put another view-specific code inside Activity. Another bad idea. Other thing to point out, is that if you are using Xaml you should also use MVVM pattern, for what Xaml was designed and when it really shine. In such case, doing GPS in above way would made things even more complicated since after obtaining a Xamarin view, you would be forced to obtain view model and only then update view model with updated GPS location.

But there is a better way to do things.

 

Reusable Android Service

Goal is to make all GPS related code inside class-interface combo. Registering both in IoC container and retrieving it from there (with maybe simple initialization before using it on some view) would be ideal. Then it is possible to place it in some library and share between applications.

Let us start with implementation of ILocationListener interface. It do not have to be implemented in Activity class, It can be any object that have base type of Java.Lang.Object. Of course since this class is Android platform class LocationListener need to be placed in appropriate platform project.

public class LocationListener : Java.Lang.Object, ILocationListener
{
    public void OnLocationChanged(Location location)
    {

    }

    public void OnProviderDisabled(string provider)
    {

    }

    public void OnProviderEnabled(string provider)
    {

    }

    public void OnStatusChanged(string provider, Availability status, Bundle extras)
    {

    }
}

The only method we are really interested in is OnLocationChanged. We need to push this new location to our new service.
We can create new PCL interface ILocationService that should work as interface that needs to be resolved from IoC container for its implementation on specific platform.

public interface ILocationService
{
    event EventHandler<LocationEventArgs> LocationChanged;

    void RequestLocation();

    void StopRequests();
}

We need event of course to notify other objects about changed location. Two other methods are used as a way to on and off GPS mechanism on device. This is needed because according to google documentation it is best to have GPS enabled only then when you need it, so as soon as you will have good enough location GPS should be disabled.

Ok, now implementation. Most of the code is copied from previous implementation in activity and as an Activity class it need to be placed in Android project.

public class LocationService : ILocationService
{
    public LocationListener Listener;
    private readonly LocationManager _locMgr;
    private readonly Criteria _locationCriteria;

    public LocationService(MainActivity activity)
    {
        _locMgr = activity.GetSystemService(Context.LocationService) as LocationManager;
        Listener = new LocationListener();
        _locationCriteria = new Criteria
        {
            Accuracy = Accuracy.Coarse,
            PowerRequirement = Power.Medium
        };
        if (_locMgr == null)
        {
            throw new Exception("No LocationManager instance!");
        }
    }

    public event EventHandler<LocationEventArgs> LocationChanged;

    public void RequestLocation()
    {
        var provider = _locMgr.GetBestProvider(_locationCriteria, true);
        if (provider == null)
        {
            throw new Exception("No GPS provider could be found for given criteria!");
        }
        _locMgr.RequestLocationUpdates(provider, 2000, 1, Listener);
    }

    public void StopRequests()
    {
        _locMgr.RemoveUpdates(Listener);
    }

    protected virtual void OnLocationChanged(LocationEventArgs e)
    {
        LocationChanged?.Invoke(this, e);
    }
}

There are few changes though. Actual start of location updates is moved to RequestLocation method. Two errors are also thrown if there is a problem with GPS mechanism. Nothing major anyway.

Problem with above is that there is no connection between location update in LocationListener and LocationService. Simplest way is to create i.e. PushLocation method in LocationService and execute it from within LocationListener.

public class LocationListener : Java.Lang.Object, ILocationListener
{
    private readonly LocationService _locationService;

    public LocationListener(LocationService locationService)
    {
        _locationService = locationService;
    }

    public void OnLocationChanged(Location location)
    {
        _locationService.PushLocation(location);
    }

    public void OnProviderDisabled(string provider)
    {

    }

    public void OnProviderEnabled(string provider)
    {

    }

    public void OnStatusChanged(string provider, Availability status, Bundle extras)
    {

    }
}

To use new constructor we need to change how instance of LocationListener is created inside LocationService constructor.

Listener = new LocationListener(this);

With instance of service inside LocationListener, we can use new PushLocation method.

internal void PushLocation(Location location)
{
    OnLocationChanged(new LocationEventArgs(location));
}

This method will raise LocationChanged event and new location will be pushed to every handler. Pretty simple, but effective.

Ok. We should now test it in some application. Main page of application can have just simple label where location will be shown.

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="Service.MainPageView">

  <Label Text="{Binding Location}" 
         VerticalOptions="Center" 
         HorizontalOptions="Center"
         FontSize="40"/>

</ContentPage>

View model for above view is also nothing fancy. Just implementation of INotifyPropertyChanged and constructor designed for dependency injection of location service.

public class MainPageViewModel : INotifyPropertyChanged
{
    private readonly ILocationService _locationService;

    public MainPageViewModel(ILocationService locationService)
    {
        _locationService = locationService;
        _locationService.LocationChanged += _locationService_LocationChanged;
    }

    public string Location { get; set; }

    private void _locationService_LocationChanged(object sender, LocationEventArgs e)
    {
        Location = e.Location.Longitude + ", " + e.Location.Latitude;
        OnPropertyChanged("Location");
    }

    public void OnAppearing()
    {
        _locationService.RequestLocation();
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    public void OnDisappering()
    {
        _locationService.StopRequests();
    }
}

Location event handler (_locationService_LocationChanged) just glues two coordinates and sets new string representation of location with notification of update to view.
How inject LocationService into view model? There is a really nice library called TinyIoC. In my previous article I explained how to add PCL support to this library and this version, which will be used in this example. But if you want you can use TinyIoC official package from Android project. Personally I think this is much better, to put IoC container inside PCL and do not worry about it in platform project (which is much easier since, you do not need to create another interface which would work as proxy from PCL to TinyIoC inside platform project). You can even place it in some share library, you are using inside all of your projects and then use it without a hassle. Smile

Anyway inside Android activity class you can place initialization of service implementation.

[Activity(Label = "Service", Icon = "@drawable/icon", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
public class MainActivity : FormsApplicationActivity
{
    protected override void OnCreate(Bundle bundle)
    {
        base.OnCreate(bundle);
        Xamarin.Forms.Forms.Init(this, bundle);
        RegisterServices();
        LoadApplication(new App());
    }

    private void RegisterServices()
    {
        var container = TinyIoCContainer.Current;
        container.Register(this);
        container.Register<ILocationService, LocationService>();
    }
}

Whole IoC magic happens in RegisterServices method, where LocationService is registered with its PCL interface. This in fact still forces you to do some work inside activity, but you must admit that it is much more convenient to enable some functionality with one line of code instead of whole interface implementation. And if you really do not like this way of registering services there is Auto register feature inside TinyIoC. Also it is possible to add your own auto registration of some specific assemblies that are available in domain or some classes marked with attribute etc.

And now, finally it is possible to create Xamarin application and set main page to well MainPage Smile

public partial class App
{
    public App()
    {
        InitializeComponent();

        var mainPageViewModel = TinyIoCContainer.Current.Resolve<MainPageViewModel>();
        MainPage = new MainPageView
        {
            BindingContext = mainPageViewModel
        };
    }
}

As you can see creation of view model instance from IoC container takes care of dependency injection of ILocationService.
Now we can at last test if everything works inside our new application.

Above test was performed on an emulator, which may look a little strange with two windows etc., but I think it shows quite good how update of location of device updates label on MainPage. And I did not have to run around with tablet to change location Smile

 

Points of interests

It is really basic implementation of GPS service. Of course there is possibility to implement more sophisticated logic. In example it would be nice to add to ILocationService some platform independent way of requesting specific accuracy of GPS updates. In above code there is use or Criteria class to select location provider. It is possible to set it to GPS or NETWORK or both by some switch. Also example code is set to constant time intervals (2s as 2000 milliseconds passed to RequestLocationUpdates method of LocationManager class) or minimum distance difference between updates of 1 meter (1 passed to RequestLocationUpdates method). There is also possibility of implementation more complex algorithm of requesting location, so that it conserve battery power with best result possible like it is recommended in Android documentation.

 

 

Summary

The same way we can create Android services for more mechanisms: accelerometer, network state, software keyboard visibility etc. without implementing all of this inside activity class, which would made it bloated and code would be impossible to share easily between applications. And after all, it is much better to write something once and then reuse it, instead of copy-pasting the same code everywhere.

View model services injection is also much more sane thing to do. You cannot in easy way, update Xamarin views from Activity and this is how it is done in Xamarin official documentation. Even more mind boggling is that Xamarin documentation involves modification of Android native views for showing GPS location Smile. What is the point of using Xamarin if you do everything in native Android views anyway. Yes, it is possible to obtain Xamarin current view page inside Activity class and update it from there, but it force creating another mechanism just for that. Even more complicated would be to push new location into view model, where application view logic should be located anyway. It would be just bad pattern to do things like this. Separating that kind of platform services and injecting their interfaces inside view models is much more simpler and cleaner way.

 

You can download code for this article from link below or you can find it on GitHub.

Service.zip (58.81 kb)



Xamarin with TinyIoC

clock December 10, 2016 10:48 by author n.podbielski

Introduction


This article is builds on topics and solutions introduces in ViewFactory for Xamarin application and Xamarin Master-Detail Page articles. It shows method for easier navigation and use of services via IoC in Xamarin application.
Knowledge from previous examples is not necessary, but makes this article bit easier to understand.

For IoC container it will use TinyIoC, but not vanilla version from author GitHub repository (or from Nuget). I will use my personal fork, because this version can be used in PCL projects, which allows TinyIoC to be placed inside shared project, which makes it much easier.

[Update]Version 1.3.0 od TinyIoC suppose to have PCL support, but it does not work. [/UPDATE]

Otherwise, with original version, which is incompatible with PCL version of .NET, we would be force to place the same TinyIoC code in every platform project. It does complicate things unnecessary. With PCL enabled version this is no longer a problem.

Idea is to use IoC container to:

  1. Create seamless navigation between views
  2. Resolve application services in view models

 

Easy navigation

It is important to note, that new navigation mechanism, should work for usual Xamarin views that are descendants from Xamarin Page class and also for pages deriving from DetailPage class. Detail views are the ones that have to be placed inside master page like it was explained in Master-Detail article. With navigation mechanism like that, we can easily navigate to another from view model (which should holds that kind of logic anyway) to another page using its view model, completely oblivious to detail of view implementation.

Navigation to view model type by resolving that type from IoC container, allows to inject this model dependencies in very easy and elegant way via constructor. You need show to the user GPS location? You just need to add new constructor parameter with IGSPService and use obtained reference.

How to do it? First we need to create Xamarin application with shared project - IoCSample. In this article I will use Android platform. Rest of platform projects are not necessary. After creating projects we need to add few references to Android project: ViewFactory, ViewFactory.Android and CustomMasterDetailControl (all are available in XamarinSamples, where this article code will be added also). Those projects are neccessary to implement better navigation between views. CustomMasterDetailControl assembly contains MasterDetailControl, which will allow us to create detail views and put them inside defined master control. Detail views along with ordinary Xamarin views will allow to show how navigation is independent from views definitions. ViewFactory projects will be used internally by navigation for views creation. After that we can add TinyIoC file with IoC container definition. This class do not need any initialization. Use of static value TinyIoCContainer.Current is enough for our needs.

Now we can change default application. Here is content of App.xaml. App.xaml.cs and MainActivity.cs files, if we do things without IoC.

<?xml version="1.0" encoding="utf-8" ?>
<Application xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="IoCSample.App">
</Application>
public partial class App
{
    public App ()
    {
        InitializeComponent();
        MainPage = new MainPageView { BindingContext = new MainPageViewModel() };
    }
}
[Activity(Label = "IoCSample", Icon = "@drawable/icon", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsApplicationActivity
{
    protected override void OnCreate(Bundle bundle)
    {
        base.OnCreate(bundle);

        global::Xamarin.Forms.Forms.Init(this, bundle);
        LoadApplication(new IoCSample.App());
    }
}

We are creating two instances: view and view model to create main page for application. Goal is to use ViewFactory to create page from view model type. To do that we need to first register ViewFactory service in IoC, but because ViewFactory is defined in Android platform project (because few Reflection features are not that easily accessible from PCL and it is much easier from full .NET in platform project; however this is possible in PCL but exceeds scope of this article), we also need to register this service in platform class - MainActivity.

protected override void OnCreate(Bundle bundle)
{
    base.OnCreate(bundle);
    RegisterServices(TinyIoCContainer.Current);
    global::Xamarin.Forms.Forms.Init(this, bundle);
    LoadApplication(new IoCSample.App());
}

private void RegisterServices(TinyIoCContainer container)
{
    container.Register<IViewFactory, VFactory>();
}

IViewFactory is new interface for ViewFactory class, which will make use of it more convenient with IoC.

public interface IViewFactory
{
    UIPage CreateView<TViewModel>() where TViewModel : BaseViewModel;

    UIPage CreateView<TViewModel, TView>() where TViewModel : BaseViewModel
        where TView : UIPage;

    UIPage CreateView<TViewModel, TView>(TViewModel viewModel) where TViewModel : BaseViewModel
        where TView : UIPage;

    UIPage CreateView(Type viewModelType);
    void Init(Assembly appAssembly);
}

Then we can initialize MainPage of application, with ViewFactory.

public IViewFactory viewFactory { get; set; }

public App()
{
    var container = TinyIoCContainer.Current;
    container.BuildUp(this);
    viewFactory.Init(Assembly.GetExecutingAssembly());
    InitializeComponent();
    MainPage = viewFactory.CreateView<MainPageViewModel>();
}

After starting application we will see MainPage, which proves that ViewFactory, IoC combo work.

Ok, but still it is just creation of new value for MainPage property of application. How to implement easier navigation? If you are not planning single page application, we need Xamarin INavigation instance. Easiest way to obtain one is to create NavigationPage instance as value for MainPage instead.

MainPage = new NavigationPage(viewFactory.CreateView<MainPageViewModel>());

Now let's create another page to be able to test if new navigation works.

<?xml version="1.0" encoding="UTF-8"?>
<customMasterDetailControl:UIPage xmlns="http://xamarin.com/schemas/2014/forms" 
                                  xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                                  xmlns:customMasterDetailControl="clr-namespace:CustomMasterDetailControl;assembly=CustomMasterDetailControl"
                                  x:Class="IoCSample.PageView">
  <Label Text="{Binding Label}" HorizontalOptions="Center" VerticalOptions="Center"/>
</customMasterDetailControl:UIPage>

Next step is to create navigation between view models. We can create static class NavigationHelper for this.

public class NavigationHelper
{
    private static readonly INavigation _navigation;
    private static readonly IViewFactory _viewFactory;
    private static TinyIoCContainer _container;

    static NavigationHelper()
    {
        var container = TinyIoCContainer.Current;
        _container = container;
        _viewFactory = container.Resolve<IViewFactory>();
        _navigation = container.Resolve<INavigation>();
    }

    public static void NavigateTo<TViewModel>() where TViewModel : BaseViewModel
    {
        _navigation.PushAsync(_viewFactory.CreateView<TViewModel>());
    }

    public static void NavigateTo<TViewModel>(Action<TViewModel> init) where TViewModel : BaseViewModel
    {
        var viewModel = _container.Resolve<TViewModel>();
init(viewModel); _navigation.PushAsync(_viewFactory.CreateView(viewModel)); } }

It uses new CreateView method inside ViewFactory class.

public UIPage CreateView<TViewModel>(TViewModel viewModel)
{
    var viewModelType = viewModel.GetType();
    if (Views.ContainsKey(viewModelType))
    {
        var viewData = Views[viewModelType];
        return CreateView(viewModel, viewData.Creator);
    }
    return null;
}

Above method allows us to create page with given view model. But it would made things problematic if view model need to be initialized somehow before navigation to its page. For example product details new instance or identificator of a product. We can create new button inside MainPage which will cause navigation to new PageView with initialization action to show how this works.

New view model looks like below.

public class PageViewModel : BaseViewModel
{
    public void SetLabelText(string value)
    {
        Label = value;
    }

    public string Label { get; set; }
}

Initialization method and property that is binded to view label. Navigation to this page is implemented with new NavigationHelper class called from MainPageView button.

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition></RowDefinition>
        <RowDefinition></RowDefinition>
    </Grid.RowDefinitions>
    <Label Grid.Row="0" Text="{Binding MainText}" VerticalOptions="Center" HorizontalOptions="Center" />
    <Button Grid.Row="1" Text="To Page" Command="{Binding ToPage}" />
</Grid>
public ICommand ToPage
{
    get
    {
        return _toPage ?? (_toPage = new Command(() =>
                {
                    NavigationHelper.NavigateTo<PageViewModel>(
                        vm => vm.SetLabelText("Value from MainPageViewModel"));
                }));
    }
}

After build and run, application correctly navigates from MainPageView to PageView.

 

The same command but without initialization of view model is less complicated and looks like this.

public ICommand ToPage
{
    get
    {
        return _toPage ?? (_toPage = new Command(NavigationHelper.NavigateTo<PageViewModel>));
    }
}

There is also possibility of creating i.e. IViewModelInit interface with Init() method, but it creates another problem. How to define parameters in this method? If this will be single object type value, it forces cast to correct type to set necessary data in view model. If we do not want casting we need to create generic BaseViewModel<> class with generic method for initialization. But still it forces us to put types in two places (type definition and initialization method), which complicates code that usually changes very often and still exists problem with number of parameters (though I never saw such method with more than two parameters). This is why i decided to leave it like this.

Nevertheless, we can simplify this further by creating method for navigating inside base view model class. Ideally it would be to put inside BaseViewModel but it is referenced assembly without knowledge of NavigationHelper class. Because of that we need to create new NavigationViewModel type with those method and then change application view models base types.

public class NavigationBaseViewModel : BaseViewModel
{
    public static void NavigateTo<TViewModel>() where TViewModel : BaseViewModel
    {
        NavigationHelper.NavigateTo<TViewModel>();
    }

    public static void NavigateTo<TViewModel>(Action<TViewModel> init) where TViewModel : BaseViewModel
    {
        NavigationHelper.NavigateTo(init);
    }
}

After changing MainPageViewModel base class to type above we can rewrite ToPage command once again.

public ICommand ToPage
{
    get
    {
        return _toPage ?? (_toPage = new Command(() =>
        {
            NavigateTo<PageViewModel>(vm => vm.SetLabelText("Value from MainPageViewModel"));
        }));
    }
}

In one line we can now order our application to:

  1. Create view model instance
  2. Initialize view model instance with appropriate data from previous view
  3. Create new view
  4. Set view model in new view
  5. Show new view to the user

 

Pretty elegant solution comparing to what is required in Xamarin by default.

public ICommand ToPageXamarin
{
    get
    {
        return _toPage ?? (_toPage = new Command(() =>
        {
            var newView = new PageView();
            var newViewModel = new PageViewModel();
            newViewModel.SetLabelText("Value from MainPageViewModel");
            newView.BindingContext = newViewModel;
            var navigation = App.Current.MainPage.Navigation;
            navigation.PushAsync(newView);
        }));
    }
}

Above code gets even more comlicated if PageViewModel would have some dependecies with dependecies etc.

 

How about navigation to Detail pages?

Next step is to implement navigation to detail pages too in the same way - by single call to NavigateTo<> method. This requires us to change NavigationHelper class. Problem with static class is that we can't enforce in any way necessity of pointing out which Master page should be used. Of course you can have one single file for all of yours application, but I highly doubt it. In Most of the time, they will differ a little. Because of that, even class MasterPageControl as in previous article might not be enough since it i.e. lacks ability to put menu on the left side of application (but can be easily achieved by modification of MasterPageControl.xaml file). Of course it is possible to create static property, let's say called MasterPageViewModelType, which would point out, which master view should be used for detail pages, but this is more error prone. I prefer to make code as clear to use as possible and then throw errors in run-time. Most of the time I am end user of my own code and I do not want to think how I should implement some mechanism to 'get things done' Smile. It should be easy and obvious as much as possible. Because of that better idea is to do service for navigation as abstract base class, with abstract property, with type of master page view model.

So we need base implementation of NavigationService. Good idea is to create generic class.

public class NavigationService<TMasterViewModel> : INavigationService<TMasterViewModel>
        where TMasterViewModel : MasterDetailControlViewModel
{
}

Most of the code is copied from NavigationHelper, but to implement detail views support we need add few new things.

public TMasterViewModel MasterViewModel
{
    get
    {
        var page = _navigation.NavigationStack.LastOrDefault();
        return page?.BindingContext as TMasterViewModel;
    }
}

Above property returns instance of master view model as type defined in class definition. Instance is not null only if there is already master page on top of a stack. Why? Because Xamarin INavigation do not allow the same page to be twice in the views stack (which makes sense) and this is why we need to create new master page every time detail page is added after ordinary, fullscreen page. Think of this as:

Master(Detail1) -> Full screen page -> Master(Detail2)

If you want to push new detail page on stack like above you can reuse last master page. But if you want to push new detail page on top, after 'full screen page' is on top, you cannot reuse previous one because it is already in other position. Therefore new instance has to be created.

With this knowledge we can implement new NavigateTo<> methods.

public void NavigateTo<TViewModel>() where TViewModel : BaseViewModel
{
    PushPage<TViewModel>(_viewFactory.CreateView<TViewModel>());
}

public void NavigateTo<TViewModel>(Action<TViewModel> init) where TViewModel : BaseViewModel
{
    var viewModel = _container.Resolve<TViewModel>();
    init(viewModel);
    PushPage<TViewModel>(_viewFactory.CreateView(viewModel));
}

public void PushPage<TViewModel>(Page page) where TViewModel : BaseViewModel
{
    if (!_viewFactory.IsDetailView<TViewModel>())
    {
        _navigation.PushAsync(page);
    }
    else
    {
        var masterViewModel = MasterViewModel;
        UIPage masterPage = null;
        if (masterViewModel == null)
        {
            masterPage = _viewFactory.CreateView<TMasterViewModel>();
            masterViewModel = (TMasterViewModel)masterPage.BindingContext;
        }
        masterViewModel.Detail = page;
        if (MasterViewModel == null)
        {
            _navigation.PushAsync(masterPage);
        }
    }
}

New PushPage method takes care of previous call to INavigation.PushAsync for ordinary (full screen) pages.

It uses new IsDetailView method of IViewFactory interface to found if page is detail view.

public bool IsDetailView(Type viewModelType)
{
    return Views[viewModelType].IsDetail;
}

public bool IsDetailView<TViewModel>() where TViewModel : BaseViewModel
{
    return IsDetailView(typeof(TViewModel));
}

ViewFactory already contains information if view is detail view or not. We just needed new method to extract this information from service.

If above method returns true for some view model it means that it has to be handled differently and cannot be directly pushed into INavigation stack. This different logic is handled by else clause in INavigationService.PushPage method. If there is no master page on top - it is created (as there was explained above) along with appropriate view by ViewFactory. After that new detail page is set in master and finally master page is pushed into navigation stack of Xamarin (if is not there yet). That explained, here is whole class will look like.

public class NavigationService<TMasterViewModel> : INavigationService<TMasterViewModel>
    where TMasterViewModel : MasterDetailControlViewModel
{
    private readonly TinyIoCContainer _container;

    private readonly INavigation _navigation;

    private readonly ViewSwitcher.IViewFactory _viewFactory;

    public NavigationService()
    {
        var container = TinyIoCContainer.Current;
        _container = container;
        _viewFactory = container.Resolve<ViewSwitcher.IViewFactory>();
        _navigation = container.Resolve<INavigation>();
    }

    public TMasterViewModel MasterViewModel
    {
        get
        {
            var firstOrDefault = _navigation.NavigationStack.FirstOrDefault();
            return firstOrDefault?.BindingContext as TMasterViewModel;
        }
    }

    public void NavigateTo<TViewModel>() where TViewModel : BaseViewModel
    {
        PushPage<TViewModel>(_viewFactory.CreateView<TViewModel>());
    }

    public void NavigateTo<TViewModel>(Action<TViewModel> init) where TViewModel : BaseViewModel
    {
        var viewModel = _container.Resolve<TViewModel>();
        init(viewModel);
        PushPage<TViewModel>(_viewFactory.CreateView(viewModel));
    }

    public void PushPage<TViewModel>(Page page) where TViewModel : BaseViewModel
    {
        if (!_viewFactory.IsDetailView<TViewModel>())
        {
            _navigation.PushAsync(page);
        }
        else
        {
            var masterViewModel = MasterViewModel;
            UIPage masterPage = null;
            if (masterViewModel == null)
            {
                masterPage = _viewFactory.CreateView<TMasterViewModel>();
                masterViewModel = (TMasterViewModel)masterPage.BindingContext;
            }
            masterViewModel.Detail = page;
            if (MasterViewModel == null)
            {
                _navigation.PushAsync(masterPage);
            }
        }
    }
}

Now we can finally put it to use. For example new button added to PageView in similar way like in MainPageView, will navigate to new DetailView.

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition />
        <RowDefinition />
    </Grid.RowDefinitions>
    <Label Grid.Row="0" Text="{Binding Label}" HorizontalOptions="Center"
            VerticalOptions="Center"/>
    <Button Grid.Row="1" Text="To detail"  Command="{Binding ToDetailPage}" />
</Grid>

With view model like below.

public class PageViewModel : NavigationBaseViewModel
{
    private Command _toDetailPage;

    public void SetLabelText(string value)
    {
        Label = value;
    }

    public string Label { get; set; }

    public ICommand ToDetailPage
    {
        get
        {
            return _toDetailPage ?? (_toDetailPage = new Command(OnToDetailPage));
        }
    }

    private void OnToDetailPage()
    {
        NavigateTo<DetailViewModel>();
    }
}

But we need to define master page first - MasterDetailControl.

<masterDetail:MasterDetailControl 
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:masterDetail="clr-namespace:CustomMasterDetailControl;assembly=CustomMasterDetailControl"
    x:Class="IoCSample.Views.MasterDetailView">
    <masterDetail:MasterDetailControl.SideContent>
        <StackLayout>
            <Button Text="To detail page" Command="{Binding ToDetail}" />
        </StackLayout>
    </masterDetail:MasterDetailControl.SideContent>
</masterDetail:MasterDetailControl>

View model for above view looks like this.

public class MasterDetailViewModel : MasterDetailControlViewModel
{
    private ICommand _toDetai;
        
    public ICommand ToDetail
    {
        get { return _toDetai ?? (_toDetai = new Command(OnToDetail)); }
    }
        
    private void OnToDetail()
    {
        NavigationHelper.NavigateTo<Detail1ViewModel>();
    }
}

Now we need just to register new service in container, in App.xaml.cs file and we can try it out.

public void RegisterServices(TinyIoCContainer container)
{
    container.Register<INavigationService, NavigationService<MasterDetailViewModel>>();
}

After running this code we can see that, this works fine and should give result similar to image below.

 

Now, when we have Navigation implemented, we can jump to second topic.

ViewModels dependency injection.

Views are supposed to have no constructor dependencies. Models are usually just data or data containers, which data logic (add order for customer etc.). The only thing UI should have is application logic in view models. Most of the time they will obtain new data or show existing data to the user. But sometimes there is a need for some specific logic i.e. get GPS location, connect with Bluetooth device, generate pdf, show modal dialog etc. All those functions are more general and it is good idea to implement them as services that can be shared between different views and applications. Sharing can be implement by IoC container and Dependency Injection (via constructors) in view models.

What we need to do in our project to achieve that? Two things:

  1. ViewFactory should create view models from IoC container not Activator class.
  2. Register services during application initialization.

 

With those two we can add parameters to constructors with services interfaces.

Let's do first point first. It is quite easy and the only problem is that we have to create virtual method in BaseViewFactory class and overwrite it in new type defined in the same assembly as TinyIoC.

private UIPage CreateView(Type viewModelType, Func<UIPage> creator)
{
    var viewModel = CreateViewModelInstance(viewModelType);
    return CreateView(viewModel, creator);
}

protected virtual object CreateViewModelInstance(Type viewModelType)
{
    var viewModel = Activator.CreateInstance(viewModelType);
    return viewModel;
}

Above methods are part of changed BaseViewFactory. Logic is the same - create instance of type via Activator. Now, new type in IoCSample project.

public class IoCViewFactory : ViewFactory.ViewFactory.ViewFactory
{
    private readonly TinyIoCContainer _container;

    public IoCViewFactory(TinyIoCContainer container)
    {
        _container = container;
    }

    protected override object CreateViewModelInstance(Type viewModelType)
    {
        return _container.Resolve(viewModelType);
    }
}

As you can see we just had to overwrite virtual method to use IoC container instead of Activator class. This is all we had to do! Smile

Now we need to create and register some service to use it inside view models. Let's imagine that we have some mechanism to store cache data in database or in file. We can change and/or read it from any view model. For case of simplicity following class, interface is enough for our sample.

public class DataCacheService : IDataCacheService
{
    public Dictionary<string, object> DataCache { get; } = new Dictionary<string, object>();
}
public interface IDataCacheService
{
    Dictionary<string, object> DataCache { get; }
}

Really simple mechanism to save some information during runtime. We have to register this service in container. We can do this inside App.xaml.cs file.

public void RegisterServices(TinyIoCContainer container)
{
    container.Register<INavigationService, NavigationService<MasterDetailViewModel>>();
    container.Register<IDataCacheService, DataCacheService>().AsSingleton();
}

We can now share cached data by accessing service. For example we can save data in one view model - DetailViewModel and read it in another one - Detail1ViewModel. Saving is done by simple Entry control binded to view model property. Every time user change value of Entry, data is saved. Then after navigation to another view it will show saved data in its label. Below is code of mentioned view models.

public class DetailViewModel : BaseViewModel
{
    private readonly IDataCacheService _dataCacheService;
    private string _text;

    public DetailViewModel(IDataCacheService dataCacheService)
    {
        _dataCacheService = dataCacheService;
        if (_dataCacheService.DataCache.ContainsKey(CacheKey))
        {
            _text = (string)_dataCacheService.DataCache[CacheKey];
        }
    }

    public const string CacheKey = "CacheKey";

    public string Text
    {
        get { return _text; }
        set
        {
            _text = value;
            _dataCacheService.DataCache[CacheKey] = value;
        }
    }
}
public class Detail1ViewModel : BaseViewModel
{
    public Detail1ViewModel(IDataCacheService dataCacheService)
    {
        if (dataCacheService.DataCache.ContainsKey(DetailViewModel.CacheKey))
        {
            Text = (string)dataCacheService.DataCache[DetailViewModel.CacheKey];
        }
    }

    public string Text { get; private set; }
}

Really simple example. Appropriate views for view models are even simpler and there is no point listings their code.

After running new application we can test if this actually works.

 

As you can see value entered in one view is accessible in second one. Dependency injection in view models works fine with automatic navigation to views by view model types, without consideration of specifics of view definition. Smile

Cleaned and refactored code (everything in one project) you can download from here: IoCFinal.zip (77.98 kb) or from github (with other Xamarin articles in the series).

 



ViewFactory for Xamarin application

clock July 9, 2016 13:37 by author n.podbielski

First things first - Why?

First of all I do not like how creating of views and navigation works in Xamarin.

There is much boilerplate needed to just navigate from one page to another.

Typically like in Xamarin documentation we have to obtain INavigation interface instance, create instance of a new page, create instance of view model and all dependencies it requires.

When you page do not require view model things are real easy and Xamarin sample is just enough.

await Navigation.PushAsync(new LoginPage());

Right?! Easy! You want to use this framework because it so intuitive! Smile

When your page do not require sophisticated initialization nor view model have dependencies things are easy too.

await Navigation.PushAsync(new LoginPage(){ BindingContext = new LoginViewModel() });

Yes, it is possible to create view model instance via Xaml of course.

<Page.BindingContext>
    <LoginViewModel />
</Page.BindingContext>

And this is why you should never do that:

  1. Xaml intellisense is quite poor currently (as far as I know because I am using R#, which takes care of most of that)
  2. It is possible to write bad Xaml and you will NOT have any compile-time errors (and you are using strongly typed language, C# just for that, to get that kind of feedback). Those errors can be caught much later, in test or if view is rarely used, it can even travel to end user.
  3. Since you will not get any errors, you will have to specifically remember to add parameters, or other type of view model initialization. Did you ever tried to add constructor parameters to Xaml? It is possible, but complicated and I do not understand why would anyone tried that if this can be done easier and safer in code.

 

We should do it in code then.

When your view model have some dependencies (only 2 is not that much really) things becomes looks ugly.

await Navigation.PushAsync(new LoginPage()
    {
          BindingContext = new LoginViewModel(){
              AuthorizationService = new AutorizationService(new WebService(), new DataBase());
          };
    }
);

What if you navigate to the same page from more than one place? Do you create static method? It is viable solution to move view model creation to view constructor for example. Then, you could write only one line, like in example from Xamarin documentation, each time you want to navigate to this page. But still, if you have set of services and some of them are singletons, you will have to obtain instance of each of them in view or view model (much better) and change of dependencies will require few lines more. Lines that you probably wrote someplace else already. If you are lazy, like me, you probably do not want to repeat yourself. Smile

Of course in official documentation in samples INavigation instance is taken from VisualElement.Navigation, but if you need to pass some parameters between pages (simplest example is to pass customer instance from previous page with list of customers, to show details of this customer in next one) this solution becomes problematic. If you bind some data to view model and want to pass it to next one, you will need to obtain that data by casting BindingContext to appropriate type, create next view model instance and set required data to it.

await Navigation.PushAsync(new MainPage()
    {
          BindingContext = new MainViewModel(){
              User = ((LoginViewModel)BindingContext).Login,
              DataBase = new DataBase(),
              GPSService = new GPSService(),
              CameraService = new CameraService(),
          };
    }
);

This is just to show problems with navigation implemented like that.

There is really simple solution to dependencies problem. It is widely used and called IoC. With container, you can create view models and set dependencies via constructor or properties. It also perfectly doable to use IoC container to obtain view model dependencies in view or in view model constructor. But again it is bad practice. IoC container should be (in ideal world) used once, for example to create first view and then work in background. Having that in mind view is not best place to use it, since Xaml requirement is, that views do not have parameters in constructors. View model it is then.

 

What would be better

Best way to navigate between views in view models is in my opinion one line with one method

Navigate<MainViewModel>();

And if you need special initialization or passed parameters from previous view model:

Navigate<MainViewModel>((mvm)=>mvm.User = Login);

Would you not agree, that this looks much nicer?

But how to do that? Of course best solution is for Navigate method to do all the initialization of view and view model, setting BindingContext to view model and pushing it to INavigation instance. Overload with action would do extra initialization of new view model.

  1. Obtain correct view for given view model type.
  2. Create view instance.
  3. Create view model instance and get all its dependencies from IoC.
  4. Set view model in view
  5. Set new view to Master page if necessary.
  6. Obtain INavigation instance and push newly created view on top of that.

 

In this article we will cover first two points, fourth and partially third one. If you are wondering what Master page is all about, you should read my previous article. In summary: we want seamless views creation and navigation between them, regardless if there are only detail views (with menu, top bar, bottom status bar etc.) or whole screen views (without menu, top bar, bottom status bar etc.). Rest will be covered in next article.

 

Implementation of ViewFactory

To ease our start with new application we can reuse some code from Master page article. This code is available on github.

To show creation of ordinary pages and detail pages, main application need only two menu items, two views and application file. We can start by creating ViewFactory shared xamarin project and add files from previous article, with just little changes.

 

File App.xaml have almost the same content as before.

<?xml version="1.0" encoding="utf-8" ?>
<Application xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="ViewFactory.App">
</Application>

Code file App.xaml.cs is very similar too.

using CustomMasterDetailControl;

namespace ViewFactory
{
    public partial class App
    {
        public App()
        {
            InitializeComponent();
            MainPage = MasterDetailControl.Create<MasterDetail, MasterDetailViewModel>();
        }
    }
}

Detail.xaml and MasterDetail.xaml are almost the same as Detail1.xaml and MasterDetail from previous article, respectively.

<?xml version="1.0" encoding="utf-8" ?>
<customMasterDetailControl:DetailPage xmlns="http://xamarin.com/schemas/2014/forms"
                                  xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                                  xmlns:customMasterDetailControl="clr-namespace:CustomMasterDetailControl;assembly=CustomMasterDetailControl"
                                  x:Class="ViewFactory.Detail">
    <Label Text="Detail" VerticalOptions="Center" HorizontalOptions="Center" />
</customMasterDetailControl:DetailPage>
<?xml version="1.0" encoding="utf-8" ?>
<customMasterDetailControl:DetailPage xmlns="http://xamarin.com/schemas/2014/forms"
                                  xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                                  xmlns:customMasterDetailControl="clr-namespace:CustomMasterDetailControl;assembly=CustomMasterDetailControl"
                                  x:Class="ViewFactory.Detail">
    <Label Text="Detail" VerticalOptions="Center" HorizontalOptions="Center" />
</customMasterDetailControl:DetailPage>

MasterDetailViewModel have only two commands to navigate to detail page and ordinary page. For now it just creates and set both pages as detail views, but we will change that after implementing ViewFactory class.

using System.Windows.Input;
using CustomMasterDetailControl;
using Xamarin.Forms;

namespace ViewFactory
{
    public class MasterDetailViewModel : MasterDetailControlViewModel
    {
        private ICommand _toDetai;

        private ICommand _toOrdinaryPage;

        public ICommand ToDetail
        {
            get { return _toDetai ?? (_toDetai = new Command(OnToDetail)); }
        }

        public ICommand ToOrdinaryPage
        {
            get { return _toOrdinaryPage ?? (_toOrdinaryPage = new Command(OnToOrdinaryPage)); }
        }

        private void OnToOrdinaryPage()
        {
            Detail = new OrdinaryPage();
        }

        private void OnToDetail()
        {
            Detail = new Detail();
        }
    }
}

OrdinaryPage derives from new UIPage class which serve as base class for DetailPage (base class of Detail page in Detail.xaml file). This way we can use one single base class for all of our views. It makes ViewFactory class a little simpler, but if it is impossible in your project, it will be simple enough to make this work with different one or even more than one base class for both types of views.

And OrdinaryPage also just have a single label inside.

<?xml version="1.0" encoding="utf-8" ?>
<masterDetail:UIPage xmlns="http://xamarin.com/schemas/2014/forms"
                         xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                         xmlns:masterDetail="clr-namespace:CustomMasterDetailControl;assembly=CustomMasterDetailControl"
                         x:Class="ViewFactory.OrdinaryPage">
  <Label Text="Ordinary page" VerticalOptions="Center" HorizontalOptions="Center" />
</masterDetail:UIPage>

One last touch to make this first version work: we need to copy and change a little CustomMasterDetailControl project.

 

BaseViewModel class is exactly what it seems - it only have implementation of INotifyPropertyChanged interface and we will need this base class for all of our view models. It is used in MasterDetailControlViewModel class instead of standalone implementation.

using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace CustomMasterDetailControl
{
    public class BaseViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

MasterDetailControlViewModel class now derives from BaseViewModel.

public class MasterDetailControlViewModel : BaseViewModel, INavigation

UIPage as mentioned above will serve as base class for all views, both partial and for whole screen:

using Xamarin.Forms;

namespace CustomMasterDetailControl
{
    public class UIPage : ContentPage
    {
    }
}
namespace CustomMasterDetailControl
{
    public class DetailPage : UIPage
    {
        public DetailPage()
        {
            SideContentVisible = true;
        }

        public bool SideContentVisible { get; set; }
    }
}

Ok. Now we can implement ViewFactory class. First thing to do is to create relations between view model types and view types. To do that, we need to scan all types and find all views. We can do it in Init method of ViewFactory class.

public static void Init()
{
    var appAssebly = Assembly.GetExecutingAssembly();
    var pages = appAssebly.DefinedTypes.Where(t => t.IfHaveBaseClassOf<UIPage>());
    var viewModels = appAssebly.DefinedTypes.Where(t => t.IfHaveBaseClassOf<BaseViewModel>()).ToArray();
    foreach (var page in pages)
    {
        var viewModel = viewModels.FirstOrDefault(vm => vm.Name == page.Name + "Model");
        if (viewModel != null)
        {
            Views.Add(viewModel, new ViewData
            {
                IsDetail = page.IfHaveBaseClassOf<DetailPage>(),
                ViewModelType = viewModel,
                ViewType = page
            });
        }
    }
}

ViewData class is just really simple data object.

public class ViewData
{
    public Type ViewType { get; set; }
    public Type ViewModelType { get; set; }
    public bool IsDetail { get; set; }
}

What is IfHaveBaseClassOf method? It is just simple Type type extension method. Contains logic for searching all base types in inheritance tree, to check if any of them is the one we are looking for.

public static bool IfHaveBaseClassOf(this Type type) where TBase : class
{
    var haveBase = false;
    var typeToCheck = type;
    var baseType = typeof(TBase);
    while (typeToCheck != null)
    {
        if (typeToCheck.BaseType == baseType)
        {
            haveBase = true;
            break;
        }
        else
        {
            typeToCheck = typeToCheck.BaseType;
        }
    }
    return haveBase;
}

It takes some type and checks if any base type in tree of inheritance is of specified type we want to find. It allows us to go through every type in assembly and check if it derive from BaseViewModel or UIPage classes which both are needed for our ViewFactory. Oh, and there is a check if view is detail view (derive from DetailPage) also with this method.

With relations between views and view models we can implement method for creating views.

public UIPage CreateView<TViewModel>() where TViewModel : BaseViewModel
{
    return CreateView(typeof(TViewModel));
}

public UIPage CreateView(Type viewModelType)
{
    if (Views.ContainsKey(viewModelType))
    {
        var viewData = Views[viewModelType];
        var page = (UIPage)Activator.CreateInstance(viewData.ViewType);
        var viewModel = Activator.CreateInstance(viewModelType);
        page.BindingContext = viewModel;
        return page;
    }
    return null;
}

As you can see it is really simple. It takes either type parameter or parameter with view model type. Then it find view type and instantiate both via Activator class. With view model it can became somewhat problematic if it does have dependencies, but it can easily be changed with IoC container, which would resolve view model with dependencies. Last thing it does, is setting view model as binding context for view.

This is just enough to have first working application with this solution. With just small change to MasterDetailViewModel commands we can now navigate via view models instead of views.

public class MasterDetailViewModel : MasterDetailControlViewModel
{
    private readonly ViewFactory.ViewFactory _viewFactory;

    private ICommand _toDetai;

    private ICommand _toOrdinaryPage;

    public MasterDetailViewModel()
    {
        _viewFactory = new ViewFactory.ViewFactory();
    }

    public ICommand ToDetail
    {
        get { return _toDetai ?? (_toDetai = new Command(OnToDetail)); }
    }

    public ICommand ToOrdinaryPage
    {
        get { return _toOrdinaryPage ?? (_toOrdinaryPage = new Command(OnToOrdinaryPage)); }
    }

    private void OnToDetail()
    {
        Detail = _viewFactory.CreateView<DetailPage>();
    }

    private void OnToOrdinaryPage()
    {
        Navigation.PushAsync(_viewFactory.CreateView<OrdinaryPage>());
    }
}

After starting this application we will see something similar to this:

 

First button will take us to detail page (menu still will be visible) and second one will take us to page for whole screen (without menu).

 

Things to improve

But there are still few points for improvements:

  1. Faster type instantiation instead of Activator
  2. Use of views and view models from libraries
  3. Some views might need specific view model type
  4. We can disable reading of all or only specific assemblies

 

Faster Views creation

Instead of using Activator.CreateInstence it is a bit faster (according to this post on StackOverflow) to use Expression.Lambda. My tests proves that it is true and compiled expression is about 25% faster than Activator.CreateInstance method. To use that technic we need to create delegate for each view. It is also possible for view model but it would not be good idea for constructors with parameters (which is something we want to do but with IoC). Let us change a little Init method and ViewData class. In ViewData we just have to add property for creator delegate:

public Func Creator { get; set; }

We need to fill this property in Init method:

Creator = (Func<UIPage>)Expression.Lambda(Expression.New(page.GetConstructor(Type.EmptyTypes))).Compile(),

It is not completely safe code, since Expression.New method expects not null parameter and Type.GetConstructor method can return null, if there is no constructor specified with given parameters (in this particular case without parameters). However this will happed only when view do not have default parameterless constructor, which view should have anyway. Anyway, in such a case Activator.CreateInstance would throw error too.

 

External libraries

Sometimes we have views and view models in different assembly than application assembly. Obviously, we want to be able to navigate to those views too. It is very easy to handle them with small change to our code. We only need to iterate through all of application assemblies and scan them as we do for application assembly. For this we can extract this scanning logic to new ScanAssembly method:

private static void ScanAssembly(Assembly appAssebly)
{
    var pages = appAssebly.DefinedTypes.Where(t => t.IfHaveBaseClassOf<UIPage>());
    var viewModels = appAssebly.DefinedTypes.Where(t => t.IfHaveBaseClassOf<BaseViewModel>()).ToArray();
    foreach (var page in pages)
    {
        var viewModel = viewModels.FirstOrDefault(vm => vm.Name == page.Name + "Model");
        if (viewModel != null)
        {
            Views.Add(viewModel, new ViewData
            {
                IsDetail = page.IfHaveBaseClassOf<DetailPage>(),
                ViewModelType = viewModel,
                ViewType = page,
                Creator =
                    (Func<UIPage>)Expression.Lambda(Expression.New(page.GetConstructor(Type.EmptyTypes))).Compile(),
            });
        }
    }
}

And we just need to iterate through all of assemblies in Init method.

var assemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach (var assembly in assemblies)
{
    ScanAssembly(assembly);
}

With this our ViewFactory will register all views for available view models in all of assemblies.

 

Overriding external views

But sometimes, if you have some library with predefined views and view models for them (for login page i.e.), you do not want to use default view and instead use other one that will be more suitable for application. It would be nice to be able to override those default views. Best way to do that, would be to create view with the same name in application assembly and let ViewFactory do the rest - scanning and overriding the default.

First, we have to modify a little our scanning method.

private static void ScanAssembly(Assembly appAssebly, bool @override = false)
{
    var pages = appAssebly.DefinedTypes.Where(t => t.IfHaveBaseClassOf<UIPage>());
    var viewModels = appAssebly.DefinedTypes.Where(t => t.IfHaveBaseClassOf<BaseViewModel>()).ToArray();
    foreach (var page in pages)
    {
        var viewModel = viewModels.FirstOrDefault(vm => vm.Name == page.Name + "Model");
        if (viewModel != null)
        {
            if (!Views.ContainsKey(viewModel))
            {
                Views.Add(viewModel, GetViewData(viewModel, page));
            }
        }
        //no view model in assembly and if app assembly -> override
        else if (@override)
        {
            var viewToOverride = Views.FirstOrDefault(kv => kv.Key.Name == page.Name + "Model");
            if (viewToOverride.Key != null)
            {
                Views[viewToOverride.Key] = GetViewData(viewToOverride.Value.ViewModelType, page);
            }
        }
    }
}

New else clause do exactly that. If view model is not found in assembly, and this assembly is main application assembly (override parameter is equal true), ViewFactory looks through its current registration list and override if there is already view model with name similar to view name.

Before last improvement (scanning only specific assemblies), we can test other, new features.

We can create new PCL project named ExternalAssembly with ExternalView, ToOverrideView and appropriate view models for them.

 

In ExternalView and ToOverrideView files we have following content:

<?xml version="1.0" encoding="utf-8" ?>
<customMasterDetailControl:DetailPage xmlns="http://xamarin.com/schemas/2014/forms"
                                  xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                                  xmlns:customMasterDetailControl="clr-namespace:CustomMasterDetailControl;assembly=CustomMasterDetailControl"
                                  x:Class="ExternalAssembly.ExternalView">
    <Label Text="External page" VerticalOptions="Center" HorizontalOptions="Center"
           FontSize="40"/>
</customMasterDetailControl:DetailPage>
<?xml version="1.0" encoding="utf-8" ?>
<customMasterDetailControl:DetailPage xmlns="http://xamarin.com/schemas/2014/forms"
                                  xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                                  xmlns:customMasterDetailControl="clr-namespace:CustomMasterDetailControl;assembly=CustomMasterDetailControl"
                                  x:Class="ExternalAssembly.ToOverrideView">
    <Label Text="Not overriden view" VerticalOptions="Center" HorizontalOptions="Center"
           FontSize="40"/>
</customMasterDetailControl:DetailPage>

Nothing really fancy - just a big labels with simple text, which is just enough to see if this works. View models for those views are just classes that derives from BaseViewModel.

For ViewFactory, to be able to see new project, we need to add reference to ViewFactory.Droid project. Also we need to add buttons to navigate to those new views and commands to handle them:

<Button Text="To external page" Command="{Binding ToExternalPage}" />
<Button Text="To overriden page" Command="{Binding ToOverridenPage}" />
private ICommand _toExternalPage;
private ICommand _toOverridenPage;

public ICommand ToExternalPage
{
    get { return _toExternalPage ?? (_toExternalPage = new Command(OnToExternal)); }
}

public ICommand ToOverridenPage
{
    get { return _toOverridenPage ?? (_toOverridenPage = new Command(OnToOverriden)); }
}

private void OnToExternal()
{
    Detail = _viewFactory.CreateView<ExternalViewModel>();
}

private void OnToOverriden()
{
    Detail = _viewFactory.CreateView<ToOverrideViewModel>();
}

After running this code and trying new buttons we will see something similar to following screenshots.

Now we should try to override ToOverrideView. To do that we need to create view with the same name in ViewFactory.Droid assembly.

<?xml version="1.0" encoding="utf-8" ?>
<customMasterDetailControl:DetailPage xmlns="http://xamarin.com/schemas/2014/forms"
                                  xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                                  xmlns:customMasterDetailControl="clr-namespace:CustomMasterDetailControl;assembly=CustomMasterDetailControl"
                                  x:Class="ViewFactory.ToOverrideView">
    <Label Text="Overriden view" VerticalOptions="Center" HorizontalOptions="Center"
           FontSize="40" />
</customMasterDetailControl:DetailPage>

It is almost the same as previous one with different label text. We just need to start an application and all the magic will happen automatically Smile. After clicking on last button (To overriden page) we will see different label this time.

 

 

We do not need all assemblies

Another point for improvements will be to disable scanning of all application assemblies. We don't need to scan System.Xml and other .NET dlls. One of the solutions would be to ignore those assemblies. It would not be hard to just skip them in ViewFactory, but still there are many libraries and Nuget packages that do not have views and they would be scanned anyway. To be true, there are much more assemblies without views in application than with views. Logical solution is then to look for assemblies with them to be scanned later. For example it is possible to add assembly attribute to mark assemblies with views for scanning. Simple one as below is enough.

using System;

namespace ViewFactory
{
    [AttributeUsage(AttributeTargets.Assembly)]
    public class ViewAssemblyAttribute : Attribute
    {
    }
}

But there is one problem. Since we need to mark i.e. ExternalAssembly as assembly with views we need this attribute to be accessible in this project. So we need third project with this attribute so we can reference it in ExternalAssembly. Even then new project with just a single attribute do not makes sense. Best thing would be to create new project with ViewFactory and other types it needs.
Unfortunately it is not that easy. Ideally it would be to place everything in PCL project, but portable version of .NET is very limited. There is no way to get list of assemblies, executing assembly, even acquiring base type of type is more complicated. There are two solutions we can use to get around those limitations.

  1. Split everything into PCL project and platform specific projects (with full .NET). Minus, big minus, of this solution is that you need to implement everything that requires full .NET more than once. But if you will write bigger framework for your applications you will do that at some point anyway, and if you have set of assemblies like that, you can do that very easily. IoC also makes this easier since you can create services for every specific part of .NET support that do not exists in PCL version (like IAdvancedReflection for part of reflection support that exists only in full .NET)
  2. Or you can try to remedy lacks of some properties or methods by combination of reflection, delegates, and expressions.

 

Since the first solution is really boring (and more easy, safe, testable etc.) we will cover second one first Smile. On aside note, it makes sense to use it only for single properties or methods. Writing bigger parts functionality like this would be really hard.

Remember TypesExtension class with single method IfHaveBaseClassOf? It wont work in PCL, without changes because portable .NET lacks Type.BaseType property. Oh, there is BaseType property, but on TypeInfo type. And TypeInfo have AsType method that returns corresponding Type object. But BaseType property returns Type value and there is need to cast it to TypeInfo again.

public static bool IfHaveBaseClassOf<TBase>(this Type type) where TBase : class
{
    var haveBase = false;
    var typeToCheck = type.GetTypeInfo();
    var baseType = typeof(TBase);
    while (typeToCheck != null)
    {
        if (typeToCheck.BaseType == baseType)
        {
            haveBase = true;
            break;
        }
        typeToCheck = typeToCheck.BaseType.GetTypeInfo();
    }
    return haveBase;
}

Ok, this is just juggling Type and TypeInfo, even if Type have BaseType property which is just unavailable in PCL. We could use reflection to get this property value.

var baseType = (Type)typeof(Type).GetRuntimeProperty("BaseType").GetValue(type);

But whole point of moving this to separate project was to make things faster, by reducing list of assemblies! This way, certainly, it would not become any faster. How we make it be better, then? We can make ourselves function to retrieve BaseType, the same way as we did with views constructors. There is even Expression.Property method just for that.

var parameterExpression = Expression.Parameter(typeof(Type));
GetBaseTypeFunc = (Func<Type, Type>)Expression.Lambda(Expression.Property(parameterExpression, "BaseType"), parameterExpression).Compile();

This creates delegate of function that takes Type object and returns its property BaseType. Parameter expression is used twice, because it is mentioned twice in lambda: first as parameter declaration and the as use. This may be a little confusing (at least was for me at first) so this is how this would be defined as 'usual' lambda.

(Type type)=>type.BaseType

According to my tests it is quicker than using portable .NET TypeInfo.AsType(), Type.GetTypeInfo() and TypeInfo.BaseType members. 1 million of executions of PCL version takes about 130ms against 110ms with use of delegate for getting BaseType value directly from Type. Not much of a difference, but since this method is used a lot it is worth a shot.

To cache this function we can save it to static field in static constructor.

static TypeExtensions()
{
    var parameterExpression = Expression.Parameter(typeof(Type));
    GetBaseTypeFunc = (Func<Type, Type>)Expression.Lambda(Expression.Property(
        parameterExpression, "BaseType"), parameterExpression).Compile();
}

And it is used exactly the same as BaseType property:

public static bool IfHaveBaseClassOf(this Type type) where TBase : class
{
    var haveBase = false;
    var typeToCheck = type;
    var baseType = typeof(TBase);
    while (typeToCheck != null)
    {
        if (GetBaseTypeFunc(typeToCheck) == baseType)
        {
            haveBase = true;
            break;
        }
        typeToCheck = GetBaseTypeFunc(typeToCheck);
    }
    return haveBase;
}

Ok. But this is only one missing property and code that mitigate this limitation is quite complex. Also code like that do not get checked at compile time. But splitting it in to PCL and platform specific implementations would be quite complex since it is static class and we cannot just make it abstract and override single method. Making it instance class would not make things easier too, since we can't use platform specific implementation directly in PCL. It would force us to write interface in PCL and then use its platform implementation via for example static reference in App class. This would be even more complex than compiling the delegate, and quite few things to do just to use one unavailable property. With solution as above we at least have it contained into one class and one file and we can forget about this inconvenience. It works and with few tests for this method it should be safe.

Splitting rest of classes between PCL and platform implementation will not be that complicated. TypeExtensions, ViewAssemblyAttribute, ViewData can be placed in PCL without changes. ViewFactory have to split into two parts. PCL version will be called BaseViewFactory and platform specific (like Android in sample) will be just ViewFactory.

There are two things that need platform specific implementation:

  1. Type.GetContstructor(Typep[]) method is not available in PCL
  2. AppDomain class is not available in PCL
  3. Assembly.GetExecutingAssembly method is not available in PCL

 

For first two we have to create abstract methods (and BaseViewFactory need to be abstract by extent).

protected abstract Assembly[] GetViewAssemblies();

protected abstract ConstructorInfo GetDefaultConstructor(Type page)

In platform version of ViewFactory we have:

public class ViewFactory : BaseViewFactory
{
    protected override Assembly[] GetViewAssemblies()
    {
        return AppDomain.CurrentDomain.GetAssemblies();
    }

    protected override ConstructorInfo GetDefaultConstructor(Type page)
    {
        return page.GetConstructor(Type.EmptyTypes);
    }
}

In summary it is the same code that was previously in single ViewFactory class, but now is called via abstract methods. Unfortunately calling those two method forces as to make Init method non static. And this forces us to make some static reference to ViewFactory in example in App class or use Singleton pattern. For case of sample simplicity static instance will suffice.

public partial class App
{
    public static readonly VF ViewFactory = new VF();
    public App()
    {
        InitializeComponent();
        ViewFactory.Init();
        MainPage = MasterDetailControl.CreateMainPage<MasterDetail, MasterDetailViewModel>();
        Navigation = MainPage.Navigation;
    }
}

As you can see it is just static global instance. Nothing fancy. IoC would allow us to make it much better.

Another problem with new code is that, since it do not reside in application assembly, we do not have easy access to it. Calling code like below in ViewFactory class (platform part):

Debug.WriteLine(Assembly.GetExecutingAssembly());
Debug.WriteLine(Assembly.GetCallingAssembly());
Debug.WriteLine(Assembly.GetEntryAssembly());

will give us following result:

[0:] ViewFactory.Android, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
[0:] ViewFactory, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
[0:]

First is Android platform version of ViewFactory project, second is PCL ViewFactory assembly, and third is null. Of course this assembly is inside loaded assemblies list and we could find it by App or MainAcitivity types, but we would have to iterate both: assemblies and types in each of them, and we wanted not to do that.
Simple way to solve this issue it to just call Assembly.GetExecutingAssembly() inside application assembly and pass return value to ViewFactory. Let us change Init method once again and pass appropriate value inside App type constructor.

public void Init(Assembly appAssembly)

ViewFactory.Init(Assembly.GetExecutingAssembly());

And this is why we do not need to fix lack of Assembly.GetExecutingAssembly method in PCL. We do not need to call it there anyway.

 

Non standard view model - view connection

It might be case when there is need to have connection between view model and view other than by name convention (or if this is just impossible to rename view or view model). For this case it would be nice to have possibility of defining view type. We can solve this by adding new attribute for view models, that allows us to define desired view type.

[AttributeUsage(AttributeTargets.Class)]
public class ViewTypeAttribute : Attribute
{
    public ViewTypeAttribute(Type viewType)
    {
        ViewType = viewType;
    }

    public Type ViewType { get; }
}

This attribute it is used like this:

[ViewType(typeof(NoViewModelView))]
public class NoViewViewModel : BaseViewModel

Very easy and convenient. But applying it to view model forces us to change ScanAssembly method a little, so it reads view models first and then tries to found appropriate views for them.

private void ScanAssembly(Assembly appAssebly, bool @override = false)
{
    var pages = appAssebly.DefinedTypes.Where(t => t.IfHaveBaseClassOf<UIPage>()).ToArray();
    var viewModels = appAssebly.DefinedTypes.Where(t => t.IfHaveBaseClassOf<BaseViewModel>());

    foreach (var viewModel in viewModels)
    {
        var viewTypeAttribute = viewModel.GetCustomAttribute<ViewTypeAttribute>();
        var page = viewTypeAttribute != null 
            ? viewTypeAttribute.ViewType 
            : pages.FirstOrDefault(p => p.Name == viewModel.Name.Replace(ViewModelPrefix, ""))?.AsType();
        if (page != null)
        {
            var asType = viewModel.AsType();
            if (!Views.ContainsKey(asType))
            {
                Views.Add(asType, GetViewData(asType, page));
            }
        }
    }
    //no view model in assembly and if app assembly -> override
    if (@override)
    {
        foreach (var page in pages)
        {
            var viewToOverride = Views.FirstOrDefault(kv => kv.Key.Name == page.Name + ViewModelPrefix);
            if (viewToOverride.Key != null)
            {
                Views[viewToOverride.Key] = GetViewData(viewToOverride.Value.ViewModelType, page.AsType());
            }
        }
    }
}

Its pretty much the same code, but instead of iterating pages we are iterating view models.

New logic involves looking for above attribute. Code is mostly self-explanatory. First its search for attribute on view model and if it was applied just uses type defined there. If not it searches inside collection of pages in assembly. Since Assembly.DefinedTypes is collection of TypeInfo objects null propagation and AsType method is used.

var viewTypeAttribute = viewModel.GetCustomAttribute<ViewTypeAttribute>();
        var page = viewTypeAttribute != null 
            ? viewTypeAttribute.ViewType 
            : pages.FirstOrDefault(p => p.Name == viewModel.Name.Replace(ViewModelPrefix, ""))?.AsType();

There are some cases when several views are connected to the same view model. It might be some kind of wizard or other process that require several steps and each step is represent by different view. To minimize effect of state on application (many bugs in my code, including ones that are really hard to debug where connected to state of objects, corrupted or otherwise incorrect, so I can agree to some extent when someone is using phrase: 'state is evil') it is good to have the same view model for all of those views. How to accomplish that with our view factory?

The easiest way to do it is to have overload of CreateView method that takes view model and view types. Yes, it defeats purpose of view factory to get rid of views in view models navigation code, but is makes code much more readable and intuitive. Otherwise we would have to create some kind of mechanism of selecting of view from view model state that is transparent to view factory. How? It would be possible with special view model type, with method or property, that would return desired type of view. But it would be too rigid to force base class of view models just for that. More flexible would be to possibility of defining method or property visible to view factory that would return view type. In both we would have to deal with view types anyway. I was considering for some time creating attributes for views that would allow us to define method or property name and desired value returned that would indicate, that it is time for this particular view. I think that it would be viable since view model probably would have some internal state value, that would point to some particular step (number of step for example). However it would force us to place view model method name in attribute which would be hard to maintain and not very transparent.
So the most reasonable way is to just allow to define view type for view model in CreateView method.

public UIPage CreateView<TViewModel, TView>() where TViewModel : BaseViewModel
    where TView : UIPage
{
    var type = typeof(TView);
    if (!_unconnectedViews.ContainsKey(type))
    {
        _unconnectedViews[type] = GetViewCreator(type);
    }
    return CreateView(typeof(TViewModel), _unconnectedViews[type]);
}

public UIPage CreateView<TViewModel, TView>(TViewModel viewModel) where TViewModel : BaseViewModel
    where TView : UIPage
{
    var type = typeof(TView);
    if (!_unconnectedViews.ContainsKey(type))
    {
        _unconnectedViews[type] = GetViewCreator(type);
    }
    return CreateView(viewModel, _unconnectedViews[type]);
}

Those two new overloads are used, first navigate to wizard view model and view type (overload with two types) and then navigate to all subsequent steps (overload with view model instance and view type).

Private static CreateView methods needs to be changed too.

private static UIPage CreateView(Type viewModelType, Func<UIPage> creator)
{
    var viewModel = Activator.CreateInstance(viewModelType);
    return CreateView(viewModel, creator);
}

private static UIPage CreateView(object viewModel, Func<UIPage> creator)
{
    var page = creator();
    page.BindingContext = viewModel;
    return page;
}

Now we can test both, new features in action. First custom connection between view and view models. For this we should create new view and view model.

<?xml version="1.0" encoding="utf-8" ?>
<customMasterDetailControl:DetailPage xmlns="http://xamarin.com/schemas/2014/forms"
                                  xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                                  xmlns:customMasterDetailControl="clr-namespace:CustomMasterDetailControl;assembly=CustomMasterDetailControl"
                                  x:Class="ViewFactorySample.View.NoViewModelView">
    <Label Text="No view model view" VerticalOptions="Center" HorizontalOptions="Center"
           FontSize="40"/>
</customMasterDetailControl:DetailPage>
[ViewType(typeof(NoViewModelView))]
public class NoViewViewModel : BaseViewModel
{

}

Nothing very different from previous views and view models. We need just to add new button in main view:

private ICommand _toCustomConnection;

public ICommand ToCustomConnection
{
    get { return _toCustomConnection ?? (_toCustomConnection = new Command(OnToCustomConnection)); }
}

private void OnToCustomConnection()
{
    Detail = _viewFactory.CreateView<NoViewViewModel>();
}
<Button Text="To custom connection page" Command="{Binding ToCustomConnection}" />

After running changed application and clicking on new button we will see page like below.

 

Custom connection created by attribute works.

Let's test navigating to views with the same view model.

For this we have to create new view model and (at least) two views. Second view is almost the same as previous ones (with different label text). First have one button more, to allow us to test navigation to second step.

<?xml version="1.0" encoding="utf-8" ?>
<customMasterDetailControl:DetailPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:customMasterDetailControl="clr-namespace:CustomMasterDetailControl;assembly=CustomMasterDetailControl"
             x:Class="ViewFactorySample.View.Step1View">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="50"/>
        </Grid.RowDefinitions>
        <Label Grid.Row="0" Text="Step 1" VerticalOptions="Center" HorizontalOptions="Center"
         FontSize="40" />
        <Button Grid.Row="1" Text="To next step" Command="{Binding ToNextStep}"></Button>
    </Grid>
</customMasterDetailControl:DetailPage>

ToNextStep command is handled in view model.

using System.Windows.Input;
using CustomMasterDetailControl;
using ViewFactorySample.View;
using Xamarin.Forms;

namespace ViewFactorySample.ViewModels
{
    public class WizardViewModel : BaseViewModel
    {
        private ICommand _toNextStep;

        public ICommand ToNextStep
        {
            get { return _toNextStep ?? (_toNextStep = new Command(OnToNextStep)); }
        }

        private void OnToNextStep()
        {
            var mainPage = ((NavigationPage)App.Current.MainPage).CurrentPage;
            var masterPageViewModel = (MasterDetailViewModel)mainPage.BindingContext;
            masterPageViewModel.Detail = masterPageViewModel.ViewFactory.CreateView<WizardViewModel, Step2View>(this);
        }
    }
}

As you can see command is pretty complicated, but almost all of the code is to retrieve reference to MasterPageViewModel to set new page as detail. This is major pain and I will cover in next article how to achieve easy and uniform navigation to ordinary, whole screen pages and detail ones.

Last thing to make this test works is new button on main page (exactly the same as before, with different text and command). It is handled a bit differently, to show how to create page with new CreateView overload.

private void OnToWizard()
{
    Detail = _viewFactory.CreateView<WizardViewModel, Step1View>();
}

After running application and using new menu button it will navigate to first step view. Button at bottom will then navigate to second step.

 

That is all. We have working ViewFactory class that allows us to create page - view model pairs in easily and configurable way. But as you can see from MasterDetailViewModel and WizardViewModel there are still some problems with navigation to whole screen pages and detail pages. Since code is different we need to know in two places if page is detail page or not. It is redundant since we already set that by choosing base class (UIPage or DetailPage). There is need for new mechanism that takes care of that - either set page to Detail property of MasterPage or push it to navigation. Also ViewFactory always creates view models with Activator.CreateInstance which require view model to have default, parameterless constructor. We do not want that, but to fix that we need IoC container to resolve view models with dependencies.

I will cover both topics in next articles.

Sample application you can find here ViewFactory.zip (83.71 kb)

Code for this article is also available on github as previous ones from Xamarin series.



Xamarin Frame and disappearing Outline

clock April 22, 2016 14:00 by author n.podbielski

This is another article in mini series of Xamarin bugs and workarounds for them Smile. Previous one you can find here.

This one is again about Frame control. I promise that another one will be about something else Cool.

Issue can be observed when you bind BackgroundColor property of Frame control and something will trigger change of view model source property binded to background color.

But since one image is more than thousand words - here is gif with this bug.

 

Frame is quite big, but if you look near corners of it, you will see, that there are round when background is green and when background change to Red, they disappear. Outline disappear too. It is slight white line around green field (it is even less visible), but I assure you that it is there. Smile

Ok here is a code for this example in xaml App file:

<?xml version="1.0" encoding="utf-8" ?>
<Application xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="FrameBug.App">
  <Application.MainPage>
    <ContentPage>
      <Grid>
        <Grid.RowDefinitions>
          <RowDefinition Height="*"></RowDefinition>
          <RowDefinition Height="*"></RowDefinition>
        </Grid.RowDefinitions>
        <StackLayout Padding="10">
          <Frame VerticalOptions="Center" OutlineColor="White" 
                 BackgroundColor="{Binding LineColor}">
            <Label Text="Label" FontSize="60" />
          </Frame>
        </StackLayout>
        <Button Text="Change color" Grid.Row="1" Command="{Binding ChangeColor}"></Button>
      </Grid>
    </ContentPage>
  </Application.MainPage>
</Application>

One noticable thing done in code behind for this xaml file is adding BindingContext instance (App.xaml.cs file):

namespace FrameBug
{
    public partial class App
    {
        public App()
        {
            InitializeComponent();
            BindingContext = new AppViewModel();
        }
    }
}

Ok. One missing piece is AppViewModel class:

using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;
using Xamarin.Forms;

namespace FrameBug
{
    public class AppViewModel : INotifyPropertyChanged
    {
        private ICommand _changeColor;

        public event PropertyChangedEventHandler PropertyChanged;

        public ICommand ChangeColor => _changeColor ?? (_changeColor = new Command(OnChangeColor));

        public Color LineColor { get; set; } = Color.Green;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        private void OnChangeColor()
        {
            LineColor = Color.Red;
            OnPropertyChanged("LineColor");
        }
    }
}

As you may notice it is C# code from VS2015. It will not work in prior versions, but there is not really point in doing test Xamarin apps in earlier versions since Xamarin recently was released for free even in VS2015 community edition.

With this simple application added to Xamarin template solution we can reproduce bug as in above gif. But how to fix it? And why it occurs? It took some digging and Resharper help with decompiling FrameRenderer to find out why. Yes, this class that renders Frame control as plain bitmap in Android environment is source of the problem. This class (since there is no public source code I will not include disassembled source code) have private UpdateBackground method which creates custom class based on Android Drawable class. It in its bowels it renders Background with solid color from BackgroundColor property of Frame control and Outline as a line (or rather rounded rectangle) from OutlineColor property of Frame. Those methods suppose to work either on initialization or BackgroundColor, OutlineColor properties change according to code of mentioned class. All good, right? When I created very similar class, attached it to custom renderer of Frame both operations was performed correctly during debugging session. But still frame was drawn incorrectly. It took some experimentation with height and width of rectangles in bitmap and it struck me that nothing happens even with 1x1 px size, because background is rerendered in some other place. Where? Since no more code in renderer was touching drawing it had to be in base class - VisualElementRenderer<>. If you disassemble it too, you will see that it have SetBackgroundColor and it was my first suspect. This method is executed on property change of control. Which one you ask? Smile BackgroundColor property. I did not dig any further since it is virtual method. Quick test proved my suspect guilty. Adding following override method to custom renderer fixed this problem.

public override void SetBackgroundColor(Color color)
{

}

Since code in FrameRenderer and its base class was executing correctly it probably was executing in wrong order. Probably it was missed by Xamarin Team after fixing this bug and closing this one. Or maybe this method as virtual was added after fix and tests and testers missed it? Nevertheless, writing new custom renderer based on Xamarin one fix this issue:

[assembly: ExportRenderer(typeof(Frame), typeof(FrameRenderer))]
namespace FrameBug.Droid
{
    public class FrameRenderer : Xamarin.Forms.Platform.Android.FrameRenderer
    {
        public override void SetBackgroundColor(Color color)
        {
            //base.SetBackgroundColor(color);
        }
    }
}

Commented line can be used to trigger the buggy behavior if anyone is curious enough Smile.

This is another one of mine unpleasant adventures with Xamarin framework. I hope this can be helpful and spare you some time. You can find sample application below.

FrameBug.zip (52.56 kb)

Also there is available public github repository with code for this article and other ones from Xamarin series.



Xamarin Frame and Style with Padding

clock April 22, 2016 06:10 by author n.podbielski

If you are a bit advanced with Xamarin you should already now that there is possibility of styling your controls almost like in WPF.
Almost, because there is a few strange bugs involving this. For example there is a problem with styling a Padding property in Frame. Consider following Xaml of Xamarin application (I tested it in Android):

<?xml version="1.0" encoding="utf-8" ?>
<Application xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:styles="clr-namespace:Styles;assembly=Styles"
             x:Class="Styles.App">
  <Application.Resources>
    <ResourceDictionary>
      <Style TargetType="Frame" x:Key="Style">
        <Setter Property="Padding" Value="0" />
      </Style>
    </ResourceDictionary>
  </Application.Resources>
  <Application.MainPage>
    <ContentPage>
      <Grid>
        <Grid.RowDefinitions>
          <RowDefinition Height="100" />
            <RowDefinition Height="100" />
            <RowDefinition Height="100" />
          <RowDefinition Height="100" />
        </Grid.RowDefinitions>
        <Frame VerticalOptions="Center"
               Grid.Row="0"
               Padding="0">
          <Label Text="100 height" FontSize="60" />
        </Frame>
        <Frame VerticalOptions="Center" Style="{StaticResource Style}"
               Grid.Row="1"
               Padding="0">
          <Label Text="100 height" FontSize="60" />
        </Frame>
        <Frame VerticalOptions="Center" Style="{StaticResource Style}"
               Grid.Row="2">
          <Label Text="100 height" FontSize="60" />
        </Frame>
      </Grid>
    </ContentPage>
  </Application.MainPage>
</Application>

It almost the same as default (created by template) shared application - but instead of App.cs we have more advanced Xaml file.

What not suspecting (or better: excepting a normal behavior) user would except all Frames to have the same view because properties will have the same values. Should have the same values, but they are not. This code will produce following layout:

 

Last Frame do not have explicit setting for Padding in its declaration and this property is resolved to default value and its contents just do not fit in available space. Why? There is a bug for that.

But according to comments Xamarin Team is not exactly willing to fix it.

Fortunately there is a quick workaround for that. The easiest thing to do is to just add this method to new class named i.e. FixedFrame:

public class ExtendedFrame : Frame
{
    protected override void OnPropertyChanged(string propertyName = null)
    {
        // ReSharper disable once ExplicitCallerInfoArgument
        base.OnPropertyChanged(propertyName);
        if (propertyName == StyleProperty.PropertyName)
        {
            if (Style.Setters.Any(s => s.Property == PaddingProperty))
            {
                Padding = (Thickness)Style.Setters.First(s => s.Property == PaddingProperty).Value;
            }
        }
    }
}

Using this class instead of original Frame from Xamarin framework will ensure proper Padding value after applying Style to FixedFrame control. Consider changed Android application from above, with added new FixedFrame control at the end. Only property set is Style and theer is no explicit value for Padding in control declaration:

<?xml version="1.0" encoding="utf-8" ?>
<Application xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:styles="clr-namespace:Styles;assembly=Styles"
             x:Class="Styles.App">
  <Application.Resources>
    <ResourceDictionary>
      <Style TargetType="Frame" x:Key="Style">
        <Setter Property="Padding" Value="0" />
      </Style>
    </ResourceDictionary>
  </Application.Resources>
  <Application.MainPage>
    <ContentPage>
      <Grid>
        <Grid.RowDefinitions>
          <RowDefinition Height="100" />
            <RowDefinition Height="100" />
            <RowDefinition Height="100" />
          <RowDefinition Height="100" />
        </Grid.RowDefinitions>
        <Frame VerticalOptions="Center"
               Grid.Row="0"
               Padding="0">
          <Label Text="100 height" FontSize="60" />
        </Frame>
        <Frame VerticalOptions="Center" Style="{StaticResource Style}"
               Grid.Row="1"
               Padding="0">
          <Label Text="100 height" FontSize="60" />
        </Frame>
        <Frame VerticalOptions="Center" Style="{StaticResource Style}"
               Grid.Row="2">
          <Label Text="100 height" FontSize="60" />
        </Frame>
        <styles:FrameExt VerticalOptions="Center" Style="{StaticResource Style}"
               Grid.Row="3">
          <Label Text="100 height" FontSize="60" />
        </styles:FrameExt>
      </Grid>
    </ContentPage>
  </Application.MainPage>
</Application>

As a result we will have following window layout:

 

As you see last row with FixedFrame control have proper Padding property value. Smile
Simple and effective. Below sample application.

Styles.zip (324.08 kb)

Also there is available public github repository with code for this article and other ones from Xamarin series.