InternetException

About coding and whatnot.

Automatic scrolling to edited text box in Xamarin Android.

clock February 25, 2017 15:48 by author n.podbielski

Important note: this should work well with newer version of Xamarin (2.3.3). If you cannot update for some reason this article will explain workaround.

Introduction

In previous article I explained how to detect show and hide events of software keyboard in Xamarin Android. I did it in my project because of the fact that some of text boxes was hidden by software keyboard that suppose to edit contents of those text boxes. In this article I will explain how to work around this issue in some older versions of Xamarin.

Solution

First thing to do is to detect focus on Entry (single line editor) and Editor (multi line editor). Best place to do that is in the renderers. If you do not have those in your project you can create new ones. If you already have renderers probably they are probably used for other purposes. It is a good idea then to limit all functionalities that are suppose to be introduced in renderers to as little lines as possible, to limit code dependency inside of renderers. Because of that I decided to add scrolling functionality to control with single line by call to static method of static class. Below are renderers for 2 editors controls.

public class ScrollEntryRenderer : EntryRenderer
{
    protected override void OnElementChanged(ElementChangedEventArgs<Entry> e)
    {
        base.OnElementChanged(e);
        EditorsScrollingHelper.AttachToControl(Control, this);
    }
}
public class ScrollEditorRenderer : EditorRenderer
{
    protected override void OnElementChanged(ElementChangedEventArgs<Editor> e)
    {
        base.OnElementChanged(e);
        EditorsScrollingHelper.AttachToControl(Control, this);
    }
}

AttachToControl method in EditorsScrollingHelper is suppose to add scrolling functionality when there is a focus event on control. EditorsScrollingHelper is doing all the heavy lifting of this mechanism.

public static void AttachToControl(TextView control, ScrollEditorRenderer renderer)
{
    control.FocusChange += (s, e) =>
    {
        FocusChange(e, renderer.Element);
    };
}

public static void AttachToControl(TextView control, ScrollEntryRenderer renderer)
{
    control.FocusChange += (s, e) =>
    {
        FocusChange(e, renderer.Element);
    };
}

FocusChange method of EditorsScrollingHelper class saves all necessary data for actual scrolling to focused control.

private static void FocusChange(AndroidView.FocusChangeEventArgs e, XamarinView element)
{
    if (e.HasFocus)
    {
        _focusedElement = element;
        _elementHeight = element.Bounds.Height;
    }
    else _focusedElement = null;
}

Need to save reference to focused control is self-explanatory. Element height will be explained below.

Actual scrolling is done inside handler of ISoftwareKeyboardService.Show event. Handler is added inside of static constructor of EditorsScrollingHelper class.

static EditorsScrollingHelper()
{
    TinyIoCContainer.Current.Resolve<ISoftwareKeyboardService>().Show += OnKeyboardShow;
}

OnKeyboardShow handler do scrolling if there is _focusedElement saved before inside FocusChange method.

private static void OnKeyboardShow(object sender, SoftwareKeyboardEventArgs args)
{
    if (_focusedElement != null)
    {
        ScrollIfNotVisible(_focusedElement);
    }
}

If condition triggers scrolling logic only then when there is focused control - because _focusedElement field is not null. It is done this way because Show event triggers more then one time for every control focus (because detecting software keyboard show event by GlobalLayoutListener is not bulletproof). Saved element height is used inside a Show event to calculate if scroll to focused control is necessary.

public static void ScrollIfNotVisible(XamarinView element)
{
    double translationY = 0;
    var parent = element;
    while (parent != null)
    {
        translationY -= parent.Y;
        parent = parent.Parent as XamarinView;
    }
    var height = Application.Current.MainPage.Bounds.Height;
    var elementHeight = _elementHeight;
    translationY -= elementHeight;
    if (-translationY > height)
    {
        if (Math.Abs(Application.Current.MainPage.TranslationY - translationY) > 0.99)
        {
            Application.Current.MainPage.SetTranslation(
                translationY + height / 2 - elementHeight / 2);
        }
    }
}

Scrolling is done by summing all of the elements heights from the focused element, going up through visual tree to the top - window root (witch do not have parent control). This is real Y coordinate of focused control. This value, with saved element height, represent position of bottom of focused control on screen. With that information, we can compare it to available screen size (Application.Current.MainPage.Bounds.Height), which at this point should be approximately half of the screen (with other half taken by software keyboard). If translationY value is greater then screen height available for application, which means that bottom of the control is invisible, then translation of main page is applied. Translation is done by extension method.

public static void SetTranslation(this VisualElement element, double y)
{
    element.TranslationY = y;
    var rectangle = new Rectangle
    {
        Left = element.X,
        Top = element.Y,
        Width = element.Width,
        Height = element.Height + (y < 0 ? -y : 0)
    };
    element.Layout(rectangle);
}

Translation works by moving control up by y pixels and setting new higher rectangle for this control. Without it application main page would be only moved up but nothing would be rendered under it, beside the white space. To remedy this effect rectangle for main page is higher and application renders any controls that might be below focused one.

This is first half of solution that is suppose to scroll app to focused control. Other half is to scroll back when control is unfocused. This is done in SoftwareKeyboardService by executing extra code on software keyboard hide event.

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

Translation of main page back to original location is done in OnHide method.

private void OnHide()
{
    if (Application.Current.MainPage != null)
    {
        Application.Current.MainPage.SetTranslation(0);
    }
}

Setting translation to zero puts back whole application where it belongs. :)

That is it! Gif from application with working solution is below.



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 Master-Detail Page

clock April 24, 2016 08:41 by author n.podbielski

This article as previous ones (Frame With Padding, Frame Outline Problem, Inheritance from Implicit Styles) also will be about Xamarin.
This time we will inspect Master-Detail page control/mechanism. You can find official documentation for this control here.

Quick spoiler: I never used this in any production application. Why? I will explain. Whole article will be to explain why this is just not worth your time. In this and subsequent articles I will introduce much better solution.

Xamarin MasterDetailPage control

Ok lets get to work. For a test drive lets say we have to do an application that allows user to browse stuff on some online shop. Master-Detail page will be used to allow user switch to a different category of wares. According to documentation user have to introduce only 3 files (at least) to make this work. Since I am lazy I will not create whole menu but only Master view and 3 Detail views (we cannot waste time on creating nice UI just for test right? Smile)

First main application xaml 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"
             xmlns:masterDetail="clr-namespace:MasterDetail;assembly=MasterDetail"
             x:Class="MasterDetail.App">
  <Application.MainPage>
    <masterDetail:MasterPage />
  </Application.MainPage>
</Application>

This cannot be more straightforward so there is no need to explain. Lets focus on MasterPage view instead:

<?xml version="1.0" encoding="utf-8" ?>
<MasterDetailPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:masterDetail="clr-namespace:MasterDetail;assembly=MasterDetail"
             x:Class="MasterDetail.MasterPage">
  <MasterDetailPage.Master>
    <ContentPage Title = "Test">
      <StackLayout>
        <Button Text="Detail1" />
        <Button Text="Detail2" />
        <Button Text="Detail3" />
      </StackLayout>
    </ContentPage>
  </MasterDetailPage.Master>
  <MasterDetailPage.Detail>
    <NavigationPage>
      <x:Arguments>
        <masterDetail:Detail1 />
      </x:Arguments>
    </NavigationPage>
  </MasterDetailPage.Detail>
</MasterDetailPage>

This file is a little more complicated but after few seconds it is clear that intention is to have 3 'menu' buttons that send you to different Detail pages.

Single Detail page could look like this:

<?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="MasterDetail.Detail1">
  <Label Text="Detail1" VerticalOptions="Center" HorizontalOptions="Center" />
</ContentPage>

Again, nothing interesting just a label to see if correct Detail page is displayed.

Above application will start and display something similar to this:

 

Ok. I lied a little. Smile You need one more file to actually make navigation between Detail pages work. Ok lets handle this in MasterPage.xaml.cs code behind and create those details pages. For now two more files will be enough.

using System;
using Xamarin.Forms;

namespace MasterDetail
{
    public partial class MasterPage
    {
        public MasterPage()
        {
            InitializeComponent();
        }

        private void Button1_OnClicked(object sender, EventArgs e)
        {
            Detail = (new Detail1());
        }

        private void Button2_OnClicked(object sender, EventArgs e)
        {
            Detail = new NavigationPage(new Detail2());
        }

        private void Button3_OnClicked(object sender, EventArgs e)
        {
            Detail = new NavigationPage(new Detail3());
        }
    }
}
<?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="MasterDetail.Detail3">
  <Label Text="Detail3" VerticalOptions="Center" HorizontalOptions="Center" />
</ContentPage>
<?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="MasterDetail.Detail2">
  <Label Text="Detail2" VerticalOptions="Center" HorizontalOptions="Center" />
</ContentPage>

First: code behind that handles navigation. If you will read carefully you will notice some differences in handling detail pages. Two others are almost exactly the same as fist Detail page.

After running that solution you will see that there are really no difference between Detail1 and Detail2.

 

Why new NavigationPage() then? Because Xamarin documentation says:

The page type is wrapped in a NavigationPage instance in order to ensure that the icon referenced through the Icon property on the MasterPage is shown on the detail page in iOS.

Ok... But what if you do not write for iOS? They do not say. So you have to ensure iOS compatibility... you know just in case. Why you? Should not Xamarin take care of things like that?

Anyway, even after enclosing all of the detail pages inside a NavigationPage (even if just for having exactly the same view layout in all of them) it do not work quite well.

Why? Let us do quick experiment and press back button (if there is one on device) - it will cause application to go into background (or at least it does on Android). What is the cause of that? It works like that because there is change of NavigationPage with every Detail page and NavigationPage is object that handles... well navigation.

Luckily there is a rather easy fix for this too:

namespace MasterDetail
{
    public partial class MasterPage
    {
        public MasterPage()
        {
            InitializeComponent();
        }

        private void Button1_OnClicked(object sender, EventArgs e)
        {
            ChangeDetail(new Detail1());
        }

        private void Button2_OnClicked(object sender, EventArgs e)
        {
            ChangeDetail(new Detail2());
        }

        private void Button3_OnClicked(object sender, EventArgs e)
        {
            ChangeDetail(new Detail3());
        }

        private void ChangeDetail(Page page)
        {
            var navigationPage = Detail as NavigationPage;
            if (navigationPage != null)
            {
                navigationPage.PushAsync(page);
                return;
            }
            Detail = new NavigationPage(page);
        }
    }
}

Since there is a bit of logic involved a new method has been added - ChangeDetail. I think it is self explanatory: we just test if there is already NavigationPage and reuse that one.

This is again code that could be easily (and should be in my opinion) in Xamarin implementation of Detail property. But hey! There is Master-Detail mechanism right? If you unlucky to be unhappy with it - well you are unlucky. Xamarin Team certainly can't let you have virtual property Detail so you could do that a better way (they have rather unpleasant tendency to have everything internal/closed/sealed).

Let us continue main topic. Just for the sake of an example lets pretend that your manager, or marketing team, or anyone that is using your appm, thinks that application do not need that wide navigation menu. They think that 50 units will suffice and they ask if you can change it. You are starting maybe from setting it in StackLayout:

<StackLayout WidthRequest="50">
  <Button Text="Detail1" Clicked="Button1_OnClicked" />
  <Button Text="Detail2" Clicked="Button2_OnClicked" />
  <Button Text="Detail3" Clicked="Button3_OnClicked" />
</StackLayout>

But if does not work. Ok... Maybe this will work:

<ContentPage Title="Test" WidthRequest="50">
  <StackLayout WidthRequest="50">
    <Button Text="Detail1" Clicked="Button1_OnClicked" />
    <Button Text="Detail2" Clicked="Button2_OnClicked" />
    <Button Text="Detail3" Clicked="Button3_OnClicked" />
  </StackLayout>
</ContentPage>

No. Maybe MasterDetailPage.Master have property for this? Nope. Maybe MasterDetailPage? Nope. You can't do that. Xamarin developers did not think of that. You would have to forward the information that this change is impossible (or better easily done/would require one week of work Smile) for now.

After some time you maybe have another request, but with remark that this is absolutely necessary: menu have to go to the right edge.

You can check if MasterBehaviour property have something that can help you with that and according to documentation its enumeration type have following values:

  • Default – The pages are displayed using the platform default.
  • Popover – The detail page covers, or partially covers the master page.
  • Split – The master page is displayed on the left and the detail page is on the right.
  • SplitOnLandscape – A split screen is used when the device is in landscape orientation.
  • SplitOnPortrait – A split screen is used when the device is in portrait orientation.

As you see it does not anything helpful. Since there are only four other properties in this control you loose hope and explains to anyone requesting this change that it will need 2 weeks of works since you have to research new solution, implement and test it. At least.

Maybe I am pessimist but there are always changes and they in 90% about adding new features/code instead of removing so it is much more probable that you will have to change how Master and Detail mechanism work because of new requirement/device/theme etc.

Of course you can modify presentation by creating new renderer but it is really complex class and would only solve issues in one platform. No. This is not recommended solution. 

All those missing features and inability to configure anything is just only part of the problem. How many times you are writing application with Xaml files that uses only views without any view models? I personnally can't think of one. Even the smallest applications are written with few views and view models for them. Even models and database for small application are not that rare.

Lets think about above example. You have menu handlers in .xaml.cs file. I almost never use those and instead of them I use commands. Can you use them here? Not really, because you don't have access to Detail property in different object. You would have to create static property or method at least to allow yourself have access to property that is pretty much whole point of this control.
But static property can not be used in tests (at least easily) so better is to create special interface and have public access to its implementation somewhere. IoC container maybe? If you use this pattern you probably have IoC constructors in some classes, most probably in view models and those can't be created easily in view click handlers, like in above example, or in ListView.ItemSelected handler like Xamarin documentation suggests.

Best thing is to use commands and some interface in my opinion. This would be easiest thing to do to amend that discrepancy. But lets think of it from other angle.

I am big fan of separating classes in smaller parts for readability, easier maintenance, testability etc. The same thing can in my opinion can be said (to some extent) about xaml files. Can this be done in MasterPage.xaml file? For example you will decide to extract from there Master page and put it in Menu.xaml. Can you do that? Not easily because you can not move ButtonX_OnClicked handlers easily because they depend of MasterDetailPage.Detail property. Of course you can create interface the same as with view model problem, but again it shows that MasterDetailPage control creates more problems then it solves.

After reading about this control I decided not to use it because all of things covered above. In fact in my opinion this control is pretty much useless, But what to use instead?

ContentTemplate and ContentPresenter

First idea is to use newly added features of templating mechanism by ContentTemplate and ContentPresenter.

Let us add new button to MasterPage.xaml file:

<Button Text="Detail4" Clicked="Button4_OnClicked" />

To create ContentTemplate we have to add just one item to resources in our application, preferably in App.xaml:

<Application.Resources>
  <ResourceDictionary>
    <ControlTemplate x:Key="DetailTemplate">
      <Grid>
        <Grid.ColumnDefinitions>
          <ColumnDefinition Width="250"></ColumnDefinition>
          <ColumnDefinition Width="*"></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <StackLayout BackgroundColor="Red">
          <Button Text="Detail1"></Button>
          <Button Text="Detail2"></Button>
          <Button Text="Detail3"></Button>
        </StackLayout>
        <ContentPresenter Grid.Column="1">
            
        </ContentPresenter>
      </Grid>
    </ControlTemplate>
  </ResourceDictionary>
</Application.Resources>

There is only 3 buttons just to show that it is different view. Red background is for the same thing. There is no handling of buttons because of the problems with MasterDetailPage control explained above. It is just a demo for templating mechanism.

Creating forth detail page called Detail4 like this:

<?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="MasterDetail.Detail4"
             ControlTemplate="{StaticResource DetailTemplate}">
  <Label Text="Detail4" VerticalOptions="Center" HorizontalOptions="Center" />
</ContentPage>

is enough for now.

Last thing is to handle button click MasterPage.xaml.cs:

private void Button4_OnClicked(object sender, EventArgs e)
{
    App.Current.MainPage = new Detail4();
}

Yes this is also handled in xaml code behind file, but since this uses static App.Current property it can be easily moved to view model command property.

After running and trying out new button we will see something like this.

 

We have 250 width, red background. We can edit this view in every way possible for any page. We will do not have any problems with previous example change requests.

But there is other problem. What if we will have use state somehow in a way of coloring or disabling current page button? It is not that easy. Since it is a template and not separate control we can't have different BindingContext for menu and Detail page. The same view model for both sooner or later will cause some problems and it will not be most obvious one, using the same view model for all or maybe different ones but with the same values for menu?

More complex problems will be caused by complexity of menu. If you will have to i.e. 2-level menu and collapse, expand sub menus user will almost sure will see that every time after opening new detail page.

This mechanism is more usable for theming application, reusing some external library control with styles for every application etc.

If not templates then what?

Custom MasterDetail control

Yes. If you want job done well, do it your self.

First of all, requirements:

1. Control should have default view model.

2. Changing detail pages must be handled from view models.

3. Control should be created once.

4. Control should handle back button.

5. View should be allowed to override when neccessary.

 

Seems easy, right? Smile

Lets start with creating App.xamlfile and its code behind 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="CustomMasterDetail.App">
</Application>
namespace CustomMasterDetail
{
    public partial class App
    {
        public App()
        {
            InitializeComponent();
            MainPage = new MasterDetail();
        }
    }
}

Simple right? To make this correct we have to also create this MasterDetail class. But since view model is required, let us start there:

using System.ComponentModel;
using System.Runtime.CompilerServices;
 
namespace CustomMasterDetail.ViewModel
{
    public class MasterDetailViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
 
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            var handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

For now this can be any view model – it is just a minimal base implementation. We need to add Detail property:

private View _detail;
public View Detail
{
    get { return _detail; }
    set
    {
        _detail = value;
        OnPropertyChanged();
    }
}

If you wondering about OnPropertyChange call, yes it will be binded. It is not clearest implementation of View-ViewModel pattern but it is clear and simple and it will be enough for now.

Ok. Lets jump to view. We will start with simple xaml page:

<?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="CustomMasterDetail.MasterDetail">
</ContentPage>

There are two things missing:

  1. Menu
  2. Detail page presentation

To add them we need to create xaml definition for first and BindableProperty for second one. We will start with second one.

public readonly BindableProperty DetailProperty = BindableProperty.Create("Detail",typeof(ContentPage));
 
public ContentPage Detail
{
    get { return (ContentPage)GetValue(DetailProperty); }
    set { SetValue(DetailProperty, value); }
}

There is one thing that might be confusing. Detail property type is a ContentPage because of example simplicity. It could be also View because this is base type of any control that could be root of detail page layout (Grid, StackLayout etc.). Page would be also doable but it would require some magic to obtain root view from i.e. CarouselPage and still allow user to use its navigation features.

We will get back to this topic.

Menu will be the same as in previous examples.

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="250" />
        <ColumnDefinition Width="*" />
    </Grid.ColumnDefinitions>
    <StackLayout Grid.Column="0">
        <Button Text="Detail1" />
        <Button Text="Detail2" />
        <Button Text="Detail3" />
    </StackLayout>
</Grid>

Grid control allows us to add menu and detail page and controls how they are connected in very simple but yet powerful way.

We need to connect Detail properties of control and view model and we can do that with simple binding:

public MasterDetail()
{
    InitializeComponent();
    SetBinding(DetailProperty, new Binding("Detail", BindingMode.OneWay));
}

We need to do this in a code behind and not in xaml because xaml operate in base class context and we need this in our class. Yes we could do this in other xaml file (in this case in App.xaml) but then we had to do this every single time we would use this control, which would really defeat whole purpose of creating separate control

Now we can define where Detail page will be in our layout and it will be second column of Grid. Of course this can any other place in Master page – top, right, bottom. You can even create multiple parts of master page that are permanently visible, in every detail page. Doing this way you can create i.e. extra properties in master view model and in detail view models that allows you hide, show this other parts, change displayed text, add buttons and etc. This is why it is better to do it this way instead of using Xamarin poor control.

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="250" />
        <ColumnDefinition Width="*" />
    </Grid.ColumnDefinitions>
    <StackLayout Grid.Column="0">
        <Button Text="Detail1" />
        <Button Text="Detail2" />
        <Button Text="Detail3" />
    </StackLayout>
    <ContentView Grid.Column="1" x:Name="DetailContainer" />
</Grid>

Change of view model property. triggers change of View property. which is handled in BindableProperty definition via propertyChanged parameter:

public readonly BindableProperty DetailProperty = BindableProperty.Create("Detail",
    typeof(ContentPage), typeof(MasterDetailControl),
    propertyChanged: (bindable, value, newValue) =>
    {
        var masterPage = (MasterDetailControl)bindable;
        View content;
        var contentPage = (ContentPage)newValue;
        if (contentPage != null)
        {
            content = contentPage.Content;
            content.BindingContext = contentPage.BindingContext;
        }
        else content = null;
        masterPage.DetailContainer.Content = content;
        masterPage.OnPropertyChanged("SideContentVisible");
    });

Now it is much clearer why there is ContentPage type used for Detail properties in view and view model –  this type have Content property, which is set as Content of our newly added ContentView control named DetailContainer. It is important to mark why there is assignment of content BindingContext property - without it this value (in masterPage.DetailContainer.Content) would be resolved to parent value, which would be MasterDetailControl.BindingContext. We do not want that. Instead there is assigned directly detail view model to it, which is what is expected when you edit detail page xaml - it suppose to be this page view model.

Ok. As you can see we have easily configurable layout of control, Detail page easily accessible from ViewModel. However we do not have required support for INavigation interface right?

Let’s take care of it now. Easiest way will be to add this interface to view model:

public MasterDetailViewModel(INavigation navigation)
{
     _navigation = navigation;
}

private Stack _pages = new Stack();

public void InsertPageBefore(Page page, Page before)
{
    if (_pages.Contains(before))
    {
        var list = _pages.ToList();
        var indexOfBefore = list.IndexOf(before);
        list.Insert(indexOfBefore, page);
        _pages = new Stack(list);
    }
    else
    {
        _navigation.InsertPageBefore(page, before);
    }
}

public Task PopAsync()
{
    Page page = null;
    if (_pages.Count > 0)
    {
        page = _pages.Pop();
        _detail = page;
        OnPropertyChanged("Detail");
    }
    return page != null ? Task.FromResult(page) : _navigation.PopAsync();
}

public Task PopAsync(bool animated)
{
    Page page = null;
    if (_pages.Count > 0)
    {
        page = _pages.Pop();
        _detail = page;
        OnPropertyChanged("Detail");
    }
    return page != null ? Task.FromResult(page) : _navigation.PopAsync(animated);
}

public Task PopModalAsync()
{
    return _navigation.PopModalAsync();
}

public Task PopModalAsync(bool animated)
{
    return _navigation.PopModalAsync(animated);
}

public Task PopToRootAsync()
{
    var firstPage = _navigation.NavigationStack[0];
    if (firstPage is MasterDetail
        || firstPage.GetType() == typeof(MasterDetail))
    {
        _pages = new Stack(new[] { _pages.FirstOrDefault() });
        return Task.FromResult(firstPage);
    }
    return _navigation.PopToRootAsync();
}

public Task PopToRootAsync(bool animated)
{

    var firstPage = _navigation.NavigationStack[0];
    if (firstPage is MasterDetail
        || firstPage.GetType() == typeof(MasterDetail))
    {
        _pages = new Stack(new[] { _pages.FirstOrDefault() });
        return Task.FromResult(firstPage);
    }
    return _navigation.PopToRootAsync(animated);
}

public Task PushAsync(Page page)
{
    Detail = page;
    return Task.FromResult(page);
}

public Task PushAsync(Page page, bool animated)
{
    Detail = page;
    return Task.FromResult(page);
}

public Task PushModalAsync(Page page)
{
    return _navigation.PushModalAsync(page);
}

public Task PushModalAsync(Page page, bool animated)
{
    return _navigation.PushModalAsync(page, animated);
}

public void RemovePage(Page page)
{
    if (_pages.Contains(page))
    {
        var list = _pages.ToList();
        list.Remove(page);
        _pages = new Stack(list);
    }
    _navigation.RemovePage(page);
}

public IReadOnlyList ModalStack { get { return _navigation.ModalStack; } }

public IReadOnlyList NavigationStack
{
    get
    {
        if (_pages.Count == 0)
        {
            return _navigation.NavigationStack;
        }
        var implPages = _navigation.NavigationStack;
        MasterDetail master = null;
        var beforeMaster = implPages.TakeWhile(d =>
        {
            master = d as MasterDetail;
            return d is MasterDetail || d.GetType() == typeof(MasterDetail);
        }).ToList();
        beforeMaster.AddRange(_pages);
        beforeMaster.AddRange(implPages.Where(d => !beforeMaster.Contains(d)
            && d != master));
        return new ReadOnlyCollection(_navigation.NavigationStack.ToList());
    }
}

It may look a little complicated but most important are only 2 methods, PushAsync and PopAsync for navigation inside an application. Modal windows functionality is just redirected to Xamarin implementation of interface. ModalStack and NavigationStack properties may be usable sometimes. First just unifies Xamarin stack and MasterPage stack (from _pages field) into one. Rest of interface is not really helpfull in most of applications, but it is implemented as ax example even if would be possible to implement only PopAsync and PushAsync methods or even creating simpler interface only for navigation inside MasterPage.

With that kind of implementation of INavigation interface we can use it inside of OnBackButtonPressed override in MasterPage:

protected override bool OnBackButtonPressed()
{
    var viewModel = BindingContext as MasterDetailViewModel;
    if (viewModel != null)
    {
        var navigation = (INavigation)viewModel;
        navigation.PopAsync();
        return true;
    }
    return base.OnBackButtonPressed();
}

Above code fulfils our requirements. MasterDetail.xaml is easy to change and it is created only once. MasterDetailViewModel is specific view model for control and its INavigation implementation makes detail page navigation easily available from outside of control. The same interface allows handling of backbutton in control.

Next thing to do: it would be nice to have MasterDetail control in reusable portable library.

MasterDetailControl in PCL library

Placing our new control in separate PCL (Portable Class Library), allow to reuse it in multiple applications. It is very useful if you have multiple applications with similar themes. Then it is possible to place whole theme in single PCL.

Lets start with creating PCL project i.e. named CustomMasterDetailControl. We need to add xaml file and view model class to this project. Most of that code will be copied from previous example.

First, xaml file MasterDetailControl.

<?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="CustomMasterDetailControl.MasterDetailControl">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="250" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <StackLayout Grid.Column="0" x:Name="SideContentContainer" />
        <ContentView Grid.Column="1" x:Name="DetailContainer" />
    </Grid>
</ContentPage>

There is one change from previous version. Instead of having menu in StackLayout control we just assigning name to this control so now it is available in code behind.

public partial class MasterDetailControl
{
    public static readonly BindableProperty SideContentProperty = BindableProperty.Create("SideContent",
        typeof(View), typeof(MasterDetailControl), null, propertyChanged: (bindable, value, newValue) =>
        {
            var control = (MasterDetailControl)bindable;
            control.SideContentContainer.Children.Clear();
            control.SideContentContainer.Children.Add(control.SideContent);
        });

    public readonly BindableProperty DetailProperty = BindableProperty.Create("Detail",
    typeof(ContentPage), typeof(MasterDetailControl),
    propertyChanged: (bindable, value, newValue) =>
    {
        var masterPage = (MasterDetailControl)bindable;
        masterPage.DetailContainer.Content = newValue != null ?
            ((ContentPage)newValue).Content : null;
    });

    public MasterDetailControl()
    {
        InitializeComponent();
        SetBinding(DetailProperty, new Binding("Detail", BindingMode.OneWay));
    }

    public ContentPage Detail
    {
        get { return (ContentPage)GetValue(DetailProperty); }
        set { SetValue(DetailProperty, value); }
    }

    public View SideContent
    {
        get { return (View)GetValue(SideContentProperty); }
        set { SetValue(SideContentProperty, value); }
    }

    public static Page Create<TView, TViewModel>() where TView : MasterDetailControl, new()
        where TViewModel : MasterDetailControlViewModel, new()
    {
        var masterDetail = new TView();
        var navigationPage = new NavigationPage(masterDetail);
        var viewModel = new TViewModel();
        viewModel.SetNavigation(navigationPage.Navigation);
        masterDetail.BindingContext = viewModel;
        return navigationPage;
    }

    protected override bool OnBackButtonPressed()
    {
        var viewModel = BindingContext as MasterDetailControlViewModel;
        if (viewModel != null)
        {
            var navigation = (INavigation)viewModel;
            navigation.PopAsync();
            return true;
        }
        return base.OnBackButtonPressed();
    }
}

With Bindable property defined like this we can override side content of MasterDetailControl in our every application like this:

<MasterDetailControl.SideContent>
    <StackLayout>
        <Button Text="Detail1" Command="{Binding ToDetail1}" />
        <Button Text="Detail2" Command="{Binding ToDetail2}" />
        <Button Text="Detail3" Command="{Binding ToDetail3}" />
    </StackLayout>
</MasterDetailControl.SideContent>

Very easy and intuitive.

There is also new method that should be used to create instance of our control and override view model type for it:

public static Page Create<TView, TViewModel>() where TView : MasterDetailControl, new()
    where TViewModel : MasterDetailControlViewModel, new()
{
    var masterDetail = new TView();
    var navigationPage = new NavigationPage(masterDetail);
    var viewModel = new TViewModel();
    viewModel.SetNavigation(navigationPage.Navigation);
    masterDetail.BindingContext = viewModel;
    return navigationPage;
}

This method contains logic that was previously in App.xaml.cs file:

public App()
{
    InitializeComponent();
    var masterDetail = new MasterDetail();
    var navigationPage = new NavigationPage(masterDetail);
    MainPage = navigationPage;
    masterDetail.BindingContext = new MasterDetailViewModel(navigationPage.Navigation);
}

However there is no point in forcing this code in every application. Instead we can use new method in following way:

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

Best way to set MainPage of application in my opinion would be to set it in xaml file of App class, but this would force as to i.e. rewrite our control so it would derive from NavigationPage instead and then add the same Bindable properties to this new type to propagate those values to our actual control. I do not think that getting rid one line in code justifies that kind of complexity.

Of course if MasterDetailViewModel class do not have parameterless constructor (because of IoC i.e.) we can create new method that takes instance of view model type and change previous overload a little:

public static Page Create<TView, TViewModel>() where TView : MasterDetailControl, new()
    where TViewModel : MasterDetailControlViewModel, new()
{
    return Create<TView, TViewModel>(new TViewModel());
}

public static Page Create<TView, TViewModel>(TViewModel viewModel) where TView : MasterDetailControl, new()    
    where TViewModel : MasterDetailControlViewModel
{
    var masterDetail = new TView();
    var navigationPage = new NavigationPage(masterDetail);
    viewModel.SetNavigation(navigationPage.Navigation);
    masterDetail.BindingContext = viewModel;
    return navigationPage;
}

Now lets focus on base class for new control view model: MasterDetailControlViewModel. It is almost the same as in previous example, but it do not contain any logic that handles side menu functionality.

public class MasterDetailControlViewModel : INotifyPropertyChanged, INavigation
{
    private Page _detail;
    private INavigation _navigation;

    private Stack<Page> _pages = new Stack<Page>();

    public event PropertyChangedEventHandler PropertyChanged;

    public Page Detail
    {
        get { return _detail; }
        set
        {
            if (_detail != value)
            {
                _pages.Push(Detail);
                _detail = value;
                OnPropertyChanged();
            }
        }
    }

    public IReadOnlyList<Page> ModalStack { get { return _navigation.ModalStack; } }

    public IReadOnlyList<Page> NavigationStack
    {
        get
        {
            if (_pages.Count == 0)
            {
                return _navigation.NavigationStack;
            }
            var implPages = _navigation.NavigationStack;
            MasterDetailControl master = null;
            var beforeMaster = implPages.TakeWhile(d =>
            {
                master = d as MasterDetailControl;
                return master != null || d.GetType() == typeof(MasterDetailControl);
            }).ToList();
            beforeMaster.AddRange(_pages);
            beforeMaster.AddRange(implPages.Where(d => !beforeMaster.Contains(d)
                && d != master));
            return new ReadOnlyCollection<Page>(_navigation.NavigationStack.ToList());
        }
    }

    public void InsertPageBefore(Page page, Page before)
    {
        if (_pages.Contains(before))
        {
            var list = _pages.ToList();
            var indexOfBefore = list.IndexOf(before);
            list.Insert(indexOfBefore, page);
            _pages = new Stack<Page>(list);
        }
        else
        {
            _navigation.InsertPageBefore(page, before);
        }
    }

    public Task<Page> PopAsync()
    {
        Page page = null;
        if (_pages.Count > 0)
        {
            page = _pages.Pop();
            _detail = page;
            OnPropertyChanged("Detail");
        }
        return page != null ? Task.FromResult(page) : _navigation.PopAsync();
    }

    public Task<Page> PopAsync(bool animated)
    {
        Page page = null;
        if (_pages.Count > 0)
        {
            page = _pages.Pop();
            _detail = page;
            OnPropertyChanged("Detail");
        }
        return page != null ? Task.FromResult(page) : _navigation.PopAsync(animated);
    }

    public Task<Page> PopModalAsync()
    {
        return _navigation.PopModalAsync();
    }

    public Task<Page> PopModalAsync(bool animated)
    {
        return _navigation.PopModalAsync(animated);
    }

    public Task PopToRootAsync()
    {
        var firstPage = _navigation.NavigationStack[0];
        if (firstPage is MasterDetailControl
            || firstPage.GetType() == typeof(MasterDetailControl))
        {
            _pages = new Stack<Page>(new[] { _pages.FirstOrDefault() });
            return Task.FromResult(firstPage);
        }
        return _navigation.PopToRootAsync();
    }

    public Task PopToRootAsync(bool animated)
    {
        var firstPage = _navigation.NavigationStack[0];
        if (firstPage is MasterDetailControl
            || firstPage.GetType() == typeof(MasterDetailControl))
        {
            _pages = new Stack<Page>(new[] { _pages.FirstOrDefault() });
            return Task.FromResult(firstPage);
        }
        return _navigation.PopToRootAsync(animated);
    }

    public Task PushAsync(Page page)
    {
        Detail = page;
        return Task.FromResult(page);
    }

    public Task PushAsync(Page page, bool animated)
    {
        Detail = page;
        return Task.FromResult(page);
    }

    public Task PushModalAsync(Page page)
    {
        return _navigation.PushModalAsync(page);
    }

    public Task PushModalAsync(Page page, bool animated)
    {
        return _navigation.PushModalAsync(page, animated);
    }

    public void RemovePage(Page page)
    {
        if (_pages.Contains(page))
        {
            var list = _pages.ToList();
            list.Remove(page);
            _pages = new Stack<Page>(list);
        }
        _navigation.RemovePage(page);
    }

    public void SetNavigation(INavigation navigation)
    {
        _navigation = navigation;
    }

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

Anything that will involve siide menu will be in application specific code view model. Application will contains from the same detail views.

<?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="CustomMasterDetail2.DetailX">
  <Label Text="DetailX" VerticalOptions="Center" HorizontalOptions="Center" />
</ContentPage>

App.xaml file is also the same and most of its code behind was shown above.

<?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="CustomMasterDetail2.App">
</Application>
using CustomMasterDetailControl;

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

MasterDetail.xaml file is application specific implementation of control and contains menu (the same as in previous example).

<?xml version="1.0" encoding="utf-8" ?>
<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="CustomMasterDetail2.MasterDetail">
    <masterDetail:MasterDetailControl.SideContent>
        <StackLayout>
            <Button Text="Detail1" Command="{Binding ToDetail1}" />
            <Button Text="Detail2" Command="{Binding ToDetail2}" />
            <Button Text="Detail3" Command="{Binding ToDetail3}" />
        </StackLayout>
    </masterDetail:MasterDetailControl.SideContent>
</masterDetail:MasterDetailControl>

Menu logic is of course contained in view model, MasterDetailViewModel class.

public class MasterDetailViewModel : MasterDetailControlViewModel
{
    private ICommand _toDetail1;
    private ICommand _toDetail2;
    private ICommand _toDetail3;

    public ICommand ToDetail1
    {
        get
        {
            return _toDetail1 ?? (_toDetail1 = new Command(OnToDetail1));
        }
    }

    public ICommand ToDetail2
    {
        get
        {
            return _toDetail2 ?? (_toDetail2 = new Command(OnToDetail2));
        }
    }

    public ICommand ToDetail3
    {
        get
        {
            return _toDetail3 ?? (_toDetail3 = new Command(OnToDetail3));
        }
    }

    private void OnToDetail1()
    {
        Detail = new Detail1();
    }

    private void OnToDetail2()
    {
        Detail = new Detail2();
    }

    private void OnToDetail3()
    {
        Detail = new Detail3();
    }
}

After running this application effect will be the same as in previous one.

 

Best thing about this solution is great possibilities of configuration. In MasterDetailControl.xaml you can change anything you want: layout, widths, heights of elements, adds new bars on top or bottom, etc.

For example if we would want to hide menu on some detail pages, we can add new property to our control:

public bool SideContentVisible
{
    get { return Detail.SideContentVisible; }
}

Obviously for this to work there is need for above property SideContentVisible in detail page. Since our details are just ContentPage class instances we need to create new base class for details. Best way is to put it in PCL library along to our MasterDetailControl class.

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

    public bool SideContentVisible { get; set; }
}

Obviously we need to change base classes of all details for this work. It is sometimes unnecessary or not desired, so let's change property in master detail control instead.

public bool IsMenuVisible
{
    get
    {
        var detailPage = Detail as DetailPage;
        if (detailPage != null)
        {
            return detailPage.SideContentVisible;
        }
        return true;
    }
}

This will handle ContentPage details to - menu visibility will just default to true. We can now create new detail called i.e. NoMenuDetail.xaml:

<?xml version="1.0" encoding="utf-8" ?>
<masterDetail:DetailPage 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="CustomMasterDetail2.NoMenuDetail"
                         SideContentVisible="False">
  <Label Text="No menu detail page" VerticalOptions="Center" HorizontalOptions="Center" />
</masterDetail:DetailPage>

We need only to add new menu item to application MasterDetail.xaml and handle this item command in view model.

<Button Text="NoMenuDetail" Command="{Binding ToNoMenuDetail}" />
private ICommand _toNoMenuDetail;

public ICommand ToNoMenuDetail
{
    get
    {
        return _toNoMenuDetail ?? (_toNoMenuDetail = new Command(OnToNoMenuDetail));
    }
}

private void OnToNoMenuDetail()
{
    Detail = new NoMenuDetail();
}

We need, of course, to create appropriate binding for this in MasterDetailControl.xaml and notify about property change, everytime Detail property changes.

<?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="CustomMasterDetailControl.MasterDetailControl"
             x:Name="Parent">
  <Grid>
    <Grid.ColumnDefinitions>
      <ColumnDefinition Width="Auto" />
      <ColumnDefinition Width="*" />
    </Grid.ColumnDefinitions>
    <StackLayout Grid.Column="0" x:Name="SideContentContainer"
                 WidthRequest="250"
                 IsVisible="{Binding SideContentVisible, Source={x:Reference Name=Parent}}" />
    <ContentView Grid.Column="1" x:Name="DetailContainer" />
  </Grid>
</ContentPage>

Binding is done by referencing control by its xaml name "Parent", which allows us to use it for binding source. It is impossible any other way in Xamaring without custom IMarkupExtension implementation (but I strongly encourage you to do that Smile). Another thing, that is worth to explain, is Auto width of side content Grid column. Without that change this column would take space even if its content would be invisible. Width of 250 is moved to WidthRequest property of StackLayout that serve as a container for menu.

Notification about SideContentVisible property change should be done every time detail changes and it involves only one line in DetailProperty declaration.

public readonly BindableProperty DetailProperty = BindableProperty.Create("Detail",
    typeof(ContentPage), typeof(MasterDetailControl),
    propertyChanged: (bindable, value, newValue) =>
    {
        var masterPage = (MasterDetailControl)bindable;
        masterPage.DetailContainer.Content = newValue != null ?
            ((ContentPage)newValue).Content : null;
        masterPage.OnPropertyChanged("SideContentVisible");
    });

After running changed code we will see on mobile device screen something like this:

 

After clicking on new menu item menu will disapper:

 

Summary

Above examples can serve as a base to create more specific controls thanks to great flexibility of this solution. It is defined by developer and it is tottally open in contrast to Xamarin native solution with it internal functionality, not accesible to end user of Xamarin. Since navigation really happens in view model it is much more viable for bigger applications that in most cases do not have parameterless constructors for view models and other classes. Base classes for control and its view model handles basic functionality, which can be extened very easily, so there is no need to do it every time in every single application. Control is created only once, which helps conserve memory. This would be impossible if every single page had the same menu. Oh, and it handles back button well Smile.

Sample applications with code examples used in this article you can find here: XamarinSamples.zip (94.48 kb)

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

Some features might still be missing or few things might be handled better. For example navigation from view model could be done by creating view models instead of views. This is better pattern and it is much simpler since navigation requires creating only one instance of view model instead of view and view model (at least in final application logic code). This would need some mechanism for finding out proper view for view model and/or registration mechanism for those pairs. Also it would be much easier to have the same navigation for detail pages and simple, whole screen content pages. For this detail pages can identified by base class DetailPage. This would create nice and simple navigation for every page via just view models regardless of specifics of a layout of views (which should not be connected to application logic anyway).

I will cover those topics in next article.

 



Xamarin Style based on implicit Style

clock April 23, 2016 00:17 by author n.podbielski

Next article about bugs in Xamarin will touch topic of Styles. Styles for all of controls not only Frame (and if you wondering why Frame you should check first two articles about Xamarin here and here).
First of all if you are using Styles in Xamarin you probably know how to define implicit styles. If not here is a sample:

<Application.Resources>
  <ResourceDictionary>
    <Style TargetType="Label">
      <Setter Property="FontSize" Value="45" />
    </Style>
  </ResourceDictionary>
</Application.Resources>

Almost the same as in WPF. If you will, or already did, use those kind of styles extensively you probably encountered problem with inheritance from them. How to do it? First consider this xaml:

<?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="StylesInheritance.App">
  <Application.Resources>
    <ResourceDictionary>
      <Style TargetType="Label">
        <Setter Property="FontSize" Value="45" />
      </Style>
    </ResourceDictionary>
  </Application.Resources>
  <Application.MainPage>
    <ContentPage>
      <Grid>
        <Grid.Resources>
          <ResourceDictionary>
            <Style TargetType="Label">
              <Setter Property="BackgroundColor" Value="Red"></Setter>
            </Style>
          </ResourceDictionary>
        </Grid.Resources>
        <Label Text="Label Text" />
      </Grid>
    </ContentPage>
  </Application.MainPage>
</Application>

You have global implicit style for Label and want, locally for Grid, add red background too. According to this Xamarin documentation it should be possible right? Style have BaseResourceKey property, which won't be helpful since implicit styles do not have keys (actually they do but we will get back to this), but there is also property BasedOn. If you have Resharper you can even taste how this should work with Intellisense:

Should work, but it do not. Ok. Resharper inserts what it looks like a correct Xaml:

<Style TargetType="Label" BasedOn="{StaticResource {x:Type Label}}">
  <Setter Property="BackgroundColor" Value="Red"></Setter>
</Style>

 But after application starts we have (un)welcoming error:

 

Why this resource is not found? It is there, right? Notice strange key value, we will get back to that.

Maybe you will (or already did tried) to use Key property of StaticResourceExtension?

<Style TargetType="Label" BasedOn="{StaticResource Key={x:Type Label}}">
  <Setter Property="BackgroundColor" Value="Red"></Setter>
</Style>

You will see another error:

 

That was something to be expected really since Key property is a string.

If you are so distrustful as me, maybe you tried to actually inspect Application resources to check if they are really there:

 

Implicit style is there oh right. Why Framework cannot find something that put in there itself? If you are perceptive you probably noticed that Keys is collection of strings and implicit styles actually do have keys, which are FullName of target types. We will have to do figure out how to set appropriate key for this type of styles. The easiest way is:

<Style TargetType="Label" BasedOn="{StaticResource Xamarin.Forms.Label}">
  <Setter Property="BackgroundColor" Value="Red"></Setter>
</Style>

<Style TargetType="Label" BasedOn="{StaticResource Key=Xamarin.Forms.Label}">
  <Setter Property="BackgroundColor" Value="Red"></Setter>
</Style>

The problem with first solution is that Resharper marks it as invalid (yeah this is really weak problem, but I like to my code to be clean that way) and second is not even checked for validity - you can put there anything - so it is even worse since you have only runtime errors. Of cource you can also assign proper key to implicit styles and make them explicit, and then assign this key to all controls, and then assign this key to child styles, and... well... this is to much work Wink.

On the side, I really do not get why Xamarin Team decided to make Resources keys a string and not objects like in WPF. If this would be the case, second Style declaration would work just fine.

{StaticResource Key={x:Type Label}}

In my opinion this is first bug/improvement they should make.

For fixing this issue I decided to write my own StaticResourceExtension, that will correctly calculate resources keys. Unfortunately as almost everything that could be useful to override in Xamarin is sealed (see Binding i.e.; not sealed in WPF). Because of that we have to do this this way:

[ContentProperty("KeyOrType")]
public class StaticResourceExtExtension : IMarkupExtension
{
    private readonly StaticResourceExtension _xamarinStaticExtension;

    public StaticResourceExtExtension()
    {
        _xamarinStaticExtension = new StaticResourceExtension();
    }

    public object KeyOrType { get; set; }

    public object ProvideValue(IServiceProvider serviceProvider)
    {
        var type = KeyOrType as Type;
        if (type != null)
        {
            _xamarinStaticExtension.Key = type.FullName;
        }
        else
        {
            var s = KeyOrType as string;
            if (s != null)
            {
                _xamarinStaticExtension.Key = s;
            }
        }
        return _xamarinStaticExtension.ProvideValue(serviceProvider);
    }
}

This will work only in newer versions of Xamarin. For example in 1.3.3.6323 this class is internal (which is interesting how it is available in Xaml). In earlier versions you have resolve to reflection or write your own mechanism for obtaining correct resources (it is not really that complicated).

With this you declare your style like this:

<Style TargetType="Label" BasedOn="{stylesInheritance:StaticResourceExt {x:Type Label}}">
  <Setter Property="BackgroundColor" Value="Red"></Setter>
</Style>

Which will still throw and error Undecided, which is strange to me and it is second bug involving styles inheritance.

 

It is the same error as in the first try with Xamarin StaticResourceExtension. If you will debug this code you can check for KeyOrType property value:

This is the point when I just stopped amazed what I found. Laughing

Really, I expected a bug, but not like this one. If you will look closely on first screen with error, you will notice that the same thing happens in Xamarin extension (and why I think that using Xamarin is like run through minefield). Apparently Xaml parser do not expect here another extension and it threats everything as plain string instead of evaluating it and then putting it in StaticResourceExtension. However this is only one thing. Why there is no closing bracket '}'? Maybe first problem originates from second one and parser threats unclosed brackets as just string and not correctly as Xaml declaration. If we add explicitly KeyOrType property to declaration it will be resolved by parser just right.

<Style TargetType="Label" BasedOn="{stylesInheritance:StaticResourceExt KeyOrType={x:Type Label}}">
  <Setter Property="BackgroundColor" Value="Red"></Setter>
</Style>

But we did not got so far without doing it (almost) perfect! Good thing we can change how our extension behaves, in contrast to Xamarin one. We have only to resolve type from its xaml abbreviation. Luckily IServiceProvider have access for type just for that: IXamlTypeResolver. With method of this type:

Type Resolve(string qualifiedTypeName, IServiceProvider serviceProvider = null);

we can easily obain correct type.

public object ProvideValue(IServiceProvider serviceProvider)
{
    var type = KeyOrType as Type;
    if (type != null)
    {
        _xamarinStaticExtension.Key = type.FullName;
    }
    else
    {
        var s = KeyOrType as string;
        if (s != null)
        {
            const string xType = "{x:Type ";
            if (s.StartsWith(xType))
            {
                var typeName = s.Replace(xType, "");
                var xamlTypeResolver = (IXamlTypeResolver)serviceProvider.GetService(typeof(IXamlTypeResolver));
                _xamarinStaticExtension.Key = xamlTypeResolver.Resolve(typeName, serviceProvider).FullName;
            }
            else
            {
                _xamarinStaticExtension.Key = s;
            }
        }
    }
    return _xamarinStaticExtension.ProvideValue(serviceProvider);
}

And it will work. As we wanted we will see big font label on red background.

 

Of course instead of removing "{x:Type " part of string it is possible to do this:

public object ProvideValue(IServiceProvider serviceProvider)
{
    var type = KeyOrType as Type;
    if (type != null)
    {
        _xamarinStaticExtension.Key = type.FullName;
    }
    else
    {
        var s = KeyOrType as string;
        if (s != null)
        {
            const string bracket = "{";
            if (s.StartsWith(bracket))
            {
                var xamlTypeResolver = (IXamlTypeResolver)serviceProvider.GetService(typeof(IXamlTypeResolver));
                var extensionName = s.Replace(bracket, "").Split(' ')[0];
                var extension = (TypeExtension)Activator.CreateInstance(xamlTypeResolver.Resolve(extensionName, serviceProvider));
                extension.TypeName = s.Split(' ')[1];
                var typeName = extension.ProvideValue(serviceProvider).FullName;
                _xamarinStaticExtension.Key = typeName;
            }
            else
            {
                _xamarinStaticExtension.Key = s;
            }
        }
    }
    return _xamarinStaticExtension.ProvideValue(serviceProvider);
}

But I think there is no point in that. This code is much less clear and also not 100% percent bulletproof. I think it is worth to do only if you planning to add your own extension for type resolving, because styles added to resources either have Type.FullName key or just some arbitrary name as key. Previous solution is then sufficient. Below is sample application.

 StylesInheritance.zip (54.13 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.