Monday, September 18, 2017

Extension methods for Caliburn.Micro SimpleContainer

Caliburn.Micro ships with an aptly named basic inversion of control container called SimpleContainer. The container satisfies most scenarios, but I’ve discovered a few minor concerns when registering classes that support more than one interface.

Suppose I have a class that implements two interfaces: IApplicationService and IMetricsProvider:

public class MetricsService : IApplicationService, IMetricsProvider
{
    #region IApplicationService
    public void Initialize()
    {
        // initialize metrics...
    }
    #endregion

    #region IMetricsProvider
    public void IncrementMetric(string metricName)
    {
        // do something with metrics...
    }
    #endregion
}

The IApplicationService is a pattern I usually implement where I want to configure a bunch of background services during application startup, and the IMetricsProvider is a class that will be consumed elsewhere in the system. It's not a perfect example, but it'll do for our conversation...

The SimpleContainer implementation doesn't have a good way of registering this class twice without registering them as separate instances. I really want the same instance to be used for both of these interfaces. Typically, to work around this issue, I might do something like this:

var container = new SimpleContainer();

container.Singleton<IMetricsProvider,MetricsService>();

var metrics = container.GetInstance<IMetricsProvider>();
container.Instance<IApplicationService>(metrics);

This isn't ideal though it will work in trivial examples. Unfortunately, this approach can fail if the class has additional constructor dependencies. In that scenario, the order in which I register and resolve dependencies becomes critical. If you resolve in the wrong order, the container injects null instances.

To work around this issue, here's a simple extension method:

public static class SimpleContainerExtensions
{
    public static SimpleContainerRegistration RegisterSingleton<TImplementation>(this SimpleContainer container, string key = null)
    {
        container.Singleton<TImplementation>(key);
        return new SimpleContainerRegistration(container, typeof(TImplementation), key);
    }
    
    class SimpleContainerRegistration
    {
        private readonly SimpleContainer _container;
        private readonly Type _implementationType;
        private readonly string _key;
    
        public SimpleContainerRegistration(SimpleContainer container, Type type, string key)
        {
            _container = container;
            _implementationType = type;
            _key = key;
        }
    
        public SimpleContainerRegistration AlsoAs<TInterface>()
        {
            container.RegisterHandler(typeof(TInterface), key, container => container.GetInstance(_implementationType, _key));
            return this;
        }
    }
}

This registers the class as a singleton and allows me to chain additional handlers for each required interface. Like so:

var container = new SimpleContainer();

container.RegisterSingleton<MetricsService>()
    .AlsoAs<IApplicationService>()
    .AlsoAs<IMetricsProvider>();

Happy coding!

submit to reddit

Tuesday, September 05, 2017

Dynamically hiding Cells in a TableView

Suppose you have a fixed list of items that you want to display in a Xamarin.Forms TableView, but you want some rows and sections of that table to be hidden. Unfortunately, there isn’t a convenient IsVisible property on the TableSection or Cell elements that we can bind to, and the only way to manipulate them is through code. Here’s a quick look on how to collapse our Cells and Sections using an Attached Property.

Take this example layout:

<ContentPage>

    <Grid>
        <TableView Intent="Settings">
            <TableRoot>
                <TableSection Title="Group 1">
                    <SwitchCell Title="Setting 1" />
                    <SwitchCell Title="Setting 2" />
                </TableSection>
                <TableSection Title="Group 2">
                    <SwitchCell Title="Setting 3" />
                    <SwitchCell Title="Setting 4" />
                </TableSection>
            </TableRoot>
        </TableView>
    </Grid>

</ContentPage>

In the above, I have two TableSection elements that contain two very simple SwitchCell elements. A lot of the detail has been omitted for clarity, but let's assume that I want the ability to only show certain settings to the user. Perhaps each setting is controlled by a special backend entitlement logic, and I want to bind that visibility for each cell through my ViewModel.

We'll create an attached property that can hide our cells:

public class CellEx
{

    public static BindableProperty CollapsedProperty =
        BindableProperty.CreateAttached(
            "Collapsed",
            typeof(bool?),
            typeof(CellEx),
            default(bool?),
            defaultBindingMode: BindingMode.OneWay,
            propertyChanged: OnCollapsedChanged);

    public static bool GetCollapsed(BindableObject target)
    {
        return (bool)target.GetValue(CollapsedProperty);
    }

    public static void SetCollapsed(BindableObject target, bool value)
    {
        target.SetValue(CollapsedProperty, value);
    }

    private static void OnCollapsedChanged(BindableObject sender, object oldValue, object newValue)
    {
        // do work with cell
    }
}

The OnCollapsedChanged event handler is called when the bound value of the BindableProperty is first set and we’ll use it to obtain a reference to the Cell. As the binding is potentially invoked before the UI is fully initialized we’ll need to defer our changes until it’s ready:

private static void OnCollapsedChanged(BindableObject sender, object oldValue, object newValue)
{
    var view = sender as Cell;
    bool isVisible = (bool)newValue;
    if (view != null)
    {
        // the parent isn't available until the page has loaded.
        if (view.Parent == null)
        {
            view.Appearing += (o,e) => 
            {
                ToggleViewCellCollapsedState(view, isVisible);
            };
        }
        else
        {
            ToggleViewCellCollapsedState(view, isVisible);
        }
    }
}

Once we have an initialized Cell, we need to obtain a reference to the containing TableSection. As a twist, the Parent of the Cell is the root TableView, so we must traverse the entire table downward to find the correct TableSection. Since a TableView only contains a fixed list of cells, scanning the entire table shouldn't be too troublesome at all:

private static void ToggleViewCellCollapsedState(Cell cell, bool isVisible)
{
    var table = (TableView)cell.Parent;
    TableSection container = FindContainingTableSection(table, cell);
    if (container != null)
    {
        if (!isVisible)
        {
            // do work to hide cell
        }
    }
}

private static TableSection FindContainingTableSection(TableView table, Cell cell)
{
    foreach(var section in table.Root)
    {
        foreach(var child in section)
        {
            if (child == cell)
            {
                return section;
            }
        }
    }

    return null;
}

Lastly, once we've obtained the necessary references, we can simply manipulate the TableSection contents. To ensure this works on all platforms, this code must execute on the UI thread:

if (!isVisible)
{

    Device.BeginInvokeOnMainThread(() => 
    {
        // remove the cell from the section
        container.Remove(cell);

        // remove the section from the table if it's empty
        if (container.Count == 0)
        {
            table.Root.Remove(container);
        }
    });
}

We can then bind the visibility of our cells to the attached property:

<ContentPage
    xmlns:ex="clr-namespace:MyNamespace.Behaviors"
    >

    <Grid>
        <TableView>
            <TableRoot>
                <TableSection Title="Group 1">
                    <SwitchCell Title="Setting 1" ex:CellEx.Collapsed={Binding IsSetting1Visible}" />
                    <SwitchCell Title="Setting 2" ex:CellEx.Collapsed={Binding IsSetting2Visible}" />
                </TableSection>
                <TableSection Title="Group 2">
                    <SwitchCell Title="Setting 3" ex:CellEx.Collapsed={Binding IsSetting3Visible}" />
                    <SwitchCell Title="Setting 4" ex:CellEx.Collapsed={Binding IsSetting4Visible}" />
                </TableSection>
            </TableRoot>
        </TableView>
    </Grid>

</ContentPage>

This works great and can dynamically hide individual cells or an entire section if needed. The largest caveat to this approach is that it only hides cells and won’t re-introduce them into the view when the binding changes. This is entirely plausible as you could cache the cells in a local variable and re-insert them programmatically, but you’d need to remember the containing section and appropriate indexes. I’d leave that to you dear reader, or I may rise to the challenge if I determine I really want to re-activate these cells in my app.

Happy coding!

submit to reddit

Monday, July 03, 2017

Troubleshooting Xamarin iPhoneSimulator errors from Visual Studio

I’m using the Xamarin Mac BuildAgent to compile my Xamarin.Forms app from Visual Studio and test it using the device simulator on my Mac. This solution was working really well for me until one day, it wasn’t. I could compile and deploy to the Simulator fine, but I was seeing errors at runtime that simply weren’t there on Android or UWP. To make matters worse, my colleagues weren’t seeing this problem when compiling and deploying to an actual device.

The problem started when we added Azure Mobile Services to our project. The errors we were seeing weren’t very helpful at all, either a TypeLoadException or NullReferenceException and they appear when trying to invoke calls that were part of the Mobile Services client. These errors are frustrating because the error detail in Visual Studio provides little guidance as to why these calls aren’t working, especially when the same calls were working before adding the reference.

The forums and usage for Azure Mobile Services recommends that you must invoke CurrentPlatform.Init() to ensure that some of the extended assemblies that are dynamically loaded at runtime are linked into the output during compilation. I’ve seen similar problems before in WPF where the compilation process would optimize away assemblies that contain only DataTemplates and Styles – effectively, if they don’t contain byte-code then the compiler will not list the reference as a dependency.

Unfortunately our team was already using CurrentPlatform.Init() in our code, and since the error was only specific to the iPhoneSimulator it suggested an issue with the Linking used for the Debug_iPhoneSimulator configuration. Although the Linker Behavior is set to Don’t link – which effectively takes all assemblies without attempting to optimize – we added the –linkskip arguments to both the main Azure Mobile Service assembly and its platform equivalent (Microsoft.WindowsAzure.Mobile.Ext).

image

This strangely seemed to resolve the issue, at least partially. We could get further along in our app initialization before the app would hang.

Inspecting the Logs

Often, the errors from Xamarin will recommend checking the logs. There are two logs to check:

  • Xamarin Logs. These logs are found through Visual Studio: Help –> Xamarin –> Open Logs. They show diagnostics for Xamarin within Visual Studio and are especially useful for understanding deployment errors such as connectivity with the Mac BuildAgent.
  • Device Logs. These logs can be found on the Mac. Simulator: Debug –> Open System Log. These logs contain the paydirt of what’s happening with the logs and provides insights into the inner exceptions that don’t bubble up into Visual Studio.

In my case, looking at the Simulator’s Device Log showed the critical missing piece of information. I was missing a reference to a reference. I was missing a reference to System.Net.Http!

Jul 2 13:35:15 Bryans-MacBook-Pro MyAppiOS[24157]: Could not find `System.Net.Http` referenced by assembly `Xamarin.Forms.Platform.iOS, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null`. 
Jul 2 13:35:15 Bryans-MacBook-Pro MyAppiOS[24157]: Could not find `System.Net.Http` referenced by assembly `Microsoft.WindowsAzure.Mobile, Version=1.3.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35`. 
Jul 2 13:35:15 Bryans-MacBook-Pro MyAppiOS[24157]: Could not find `System.Net.Http` referenced by assembly `Xamarin.Auth, Version=1.3.0.0, Culture=neutral, PublicKeyToken=null`. 
Jul 2 13:35:15 Bryans-MacBook-Pro MyAppiOS[24157]: Could not find `System.Net.Http` referenced by assembly `Microsoft.WindowsAzure.Storage, Version=7.1.3.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35`.

The logs contain .NET StackTraces and Exception details, which provide excellent supporting information for errors that occur in the IDE. And now that the simulator actually works it’ll be used a great deal more often.

Monday, May 01, 2017

Xamarin + Parallels for Mac

Well, it took the better half of a Saturday installing updates for what seems like everything, but I managed to my MacBook Pro running Visual Studio 2015 and able to deploy to the iPhone Simulator through the Xamarin Mac Agent. I was surprised at how easy it was and only one minor caveat I had to work through…

My Saturday began with starting my Windows 10 virtual machine on my Mac and being greeted with a warning regarding a conflict between Parallels for Mac 11 and the Windows 10 Creator’s Update. I hadn’t installed the Creators Update yet, so I decided there was no better time than the present to update my Parallels installation. The installation was quick and painless.

When I started my Windows 10 virtual machine, it was a bit unresponsive so I decided to restart it. However, my only options were to “Update and Shutdown” or “Update and Restart”. Sigh. Ok, let’s update this too.

Sure enough, the update was the Windows 10 Creators Update. Well, at least I updated Parallels first.

While I was waiting for this to complete, a notification from my Mac popped up: that update that required me to be connected to power didn’t run last night. It seems like my Mac has been nagging me for a while now to perform this update and I always choose “Later”. “Later” usually means the next day I’m warned about not being connected to a power source. Sigh. Ok, fine. I’m updating Windows, why not update the Mac?

Crap. That update was for Mac Sierra which I thought I had already installed. Maybe it’ll be quick. Wishful thinking for an OS update I suppose.

Both the Mac and Windows seemed to update around the same time, almost like they were in competition of completing before the other. Finally. Let’s do this.

When I booted up Visual Studio 2015 I was surprised that the usual Xamarin menu options weren’t available. I launched the Extensions and Updates option and realized that Visual Studio Update 2 wasn’t installed. I’d forgotten that I wasn’t using my Parallels for this (hence this post) and I’d been using my PC to do most of my builds and only using the Mac for the Xamarin Mac Agent. I started the Update 3 wizard and selected the 13 GBs of features that I needed. Let’s grab some lunch while this completes.

When I came back to my Mac, Visual Studio 2015 was already running and waiting for me. I launched the Xamarin Mac Agent and it immediately found my Mac. I logged in without issues. Brilliant. I decided to tempt fate and create a new Xamarin.Forms Portable application, compile and run on my iOS simulator. After creating the solution, Xamarin informed me that there was an update available. The rate things were going, I might as well do this now, right?

While I was updating the Xamarin NuGet package, I toggled back to my Mac and checked to see if there was an update for my Xamarin Studio. Yeah, I’m pretty sure you knew there’d be one too. Lots of downloads and updates.

OK FINE. Everything’s good now. Let’s compile this bad boy and try out deploying to my mac from Visual Studio.

Sadly, I was presented with a wack of compilation errors. Turns out, parts of my Xamarin.Android weren’t being resolved during compilation. The workaround appears in Xamarin’s troubleshooting section, so it must be somewhat common. I had to download a ~200Mb file, rename it and place it a specific folder and I was back in business. Compiling took 20 minutes while everything re-unpacked and initialized.

Everything compiles! Life is good. It’s about time we deploy to my mac from Visual Studio? Sweet Christmas, now it’s complaining that my version of Xamarin requires an update to XCode 8.3.

Fortunately, XCode 8.3 is only 2.5 GB and I must really want to compile and deploy. I fire-up Fantastic Beasts and where to find them while I wait.

About half way through the movie, I check out my Mac. I reboot and had to force a reboot on my windows 10 virtual machine.

I compiled. It worked. I clicked deploy. It worked. Hopefully the benefits of only having to carry around a single device provides enough productivity gains to outweigh this productivity disaster.

Oh – I mentioned a caveat. I wasn’t able to compile the solution using the default parallels mapped folder location \\Mac\\Home\\Documents. I changed this to a different folder on the machine.

Well, there you go. I think this post was more of a rant than any helpful tip. If you made it to the end, a cookie for you, I guess. Hope you enjoyed reading my adventure. Do you think Dumbledore will battle Grindlewald in the next movie?

Monday, April 24, 2017

My favourite Visual Studio Snippets

It happens a fair bit: there’s a small identical piece of code that you have to need to include in each project you work on. Sometimes you can copy and paste from an old project or you simply write it from scratch. You do this over and over so much that you get used to writing it.

Fortunately, Visual Studio “snippets” can tame this monster. Simply type a keyword and hit tab twice and – bam! – code magically appears. But if you’re like me, the thought of deviating from your project to write a snippet can seem tedious. Lucky for you, you don’t have to write them, you can just borrow my favourite snippets.

vmbase

My vmbase snippet includes some common boilerplate code for INotifyPropertyChanged. It includes the SetField<T> method you may see in a few of my posts. After adding this snippet, be sure to decorate your class with INotifyPropertyChanged.

<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets  xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
    <CodeSnippet Format="1.0.0">
        <Header>
            <Title>Base ViewModel implementation</Title>
            <Shortcut>vmbase</Shortcut>
            <Description>Inserts SetField and NotifyPropertyChanged</Description>
            <Author>BCook</Author>
            <SnippetTypes>
                <SnippetType>Expansion</SnippetType>
            </SnippetTypes>
        </Header>
        <Snippet>
            <Imports>
                <Import>
                    <Namespace>System.Collections.Generic</Namespace>
                </Import>
                <Import>
                    <Namespace>System.ComponentModel</Namespace>
                </Import>
                <Import>
                    <Namespace>System.Runtime.CompilerServices</Namespace>
                </Import>

            </Imports>
            <Declarations>
            </Declarations>
            <Code Language="csharp"><![CDATA[
            protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
            {
                if (!EqualityComparer<T>.Default.Equals(field, value))
                {
                    field = value;
                    NotifyPropertyChanged(propertyName);
                    return true;
                }
                
                return false;
            }
            
            protected void NotifyPropertyChanged([CallerMemberName] string propertyName = null)
            {
                var handler = PropertyChanged;
                if (handler != null)
                {
                    handler(this, new PropertyChangedEventArgs(propertyName));
                }
            }
$end$]]>
            </Code>
        </Snippet>
    </CodeSnippet>
</CodeSnippets>

propvm

The propvm snippet is similar to other “prop” snippets. It creates a property with a backing field and uses the SetField<T> method for raising property change notifications.

<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets  xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
    <CodeSnippet Format="1.0.0">
        <Header>
            <Title>Define a Property with SetField</Title>
            <Shortcut>propvm</Shortcut>
            <Description>Code snippet for a ViewModel property</Description>
            <Author>Bryan Cook</Author>
            <SnippetTypes>
                <SnippetType>Expansion</SnippetType>
            </SnippetTypes>
        </Header>
        <Snippet>
            <Declarations>
                <Literal>
                    <ID>type</ID>
                    <ToolTip>Property Type</ToolTip>
                    <Default>string</Default>
                </Literal>
                <Literal>
                    <ID>property</ID>
                    <ToolTip>Property Name</ToolTip>
                    <Default>MyProperty</Default>
                </Literal>
                <Literal>
                    <ID>field</ID>
                    <ToolTip>Field Name</ToolTip>
                    <Default>myProperty</Default>
                </Literal>
            </Declarations>
            <Code Language="csharp"><![CDATA[
private $type$ _$field$;

public $type$ $property$
{
    get { return _$field$; }
    set { SetField(ref _$field$, value); }
}
$end$]]>
            </Code>
        </Snippet>
    </CodeSnippet>
</CodeSnippets>

propbp

Similar to the propdp snippet which creates WPF dependency properties, propbp creates a Xamarin.Form BindableProperty.

<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets  xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
    <CodeSnippet Format="1.0.0">
        <Header>
            <Title>Define a Xamarin.Forms Bindable Property</Title>
            <Shortcut>propbp</Shortcut>
            <Description>Code snippet for Xamarin.Forms Bindable Property</Description>
            <Author>Bryan Cook</Author>
            <SnippetTypes>
                <SnippetType>Expansion</SnippetType>
            </SnippetTypes>
        </Header>
        <Snippet>
            <Imports>
                <Import>
                    <Namespace>Xamarin.Forms</Namespace>
                </Import>
            </Imports>
            <Declarations>
                <Literal>
                    <ID>type</ID>
                    <ToolTip>Property Type</ToolTip>
                    <Default>string</Default>
                </Literal>
                <Literal>
                    <ID>property</ID>
                    <ToolTip>Property Name</ToolTip>
                    <Default>MyProperty</Default>
                </Literal>
                <Literal>
                    <ID>owner</ID>
                    <ToolTip>Owner Type for the BindableProperty</ToolTip>
                    <Default>object</Default>
                </Literal>
            </Declarations>
            <Code Language="csharp"><![CDATA[
#region $property$
public static BindableProperty $property$Property =
    BindableProperty.Create(
        "$property$",
        typeof($type$),
        typeof($owner$),
        default($type$),
        defaultBindingMode: BindingMode.OneWay,
        propertyChanged: On$property$Changed);
        
private static void On$property$Changed(BindableObject sender, object oldValue, object newValue)
{
}
#endregion

public $type$ $property$
{
    get { return ($type$)GetValue($property$Property); }
    set { SetValue($property$Property, value); }
}

$end$]]>
            </Code>
        </Snippet>
    </CodeSnippet>
</CodeSnippets>

testma

Visual Studio ships with a super helpful testm snippet which generates a MSTest test method for you. My simple testma snippet creates an asynchronous test method.

<?xml version="1.0" encoding="utf-8" ?>
<CodeSnippets  xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
    <CodeSnippet Format="1.0.0">
        <Header>
            <Title>Async Test Method</Title>
            <Shortcut>testma</Shortcut>
            <Description>Inserts Test Method with async keyword</Description>
            <Author>BCook</Author>
            <SnippetTypes>
                <SnippetType>Expansion</SnippetType>
            </SnippetTypes>
        </Header>
        <Snippet>
            <Imports>
                <Import>
                    <Namespace>Microsoft.VisualStudio.TestTools.UnitTesting</Namespace>
                </Import>
                <Import>
                    <Namespace>System.Threading.Tasks</Namespace>
                </Import>
            </Imports>
            <References>
                <Reference>
                    <Assembly>Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll</Assembly>
                </Reference>
            </References>        
            <Declarations>
                <Literal>
                    <ID>method</ID>
                    <ToolTip>MethodName</ToolTip>
                    <Default>TestMethodName</Default>
                </Literal>
                <Literal Editable="false">
                    <ID>TestMethod</ID>
                    <Function>SimpleTypeName(global::Microsoft.VisualStudio.TestTools.UnitTesting.TestMethod)</Function>
                </Literal>                
            </Declarations>
            <Code Language="csharp"><![CDATA[
            [$TestMethod$]
            public async Task $method$()
            {
                // await ...
                Assert.Fail();
            }
            
$end$]]>
            </Code>
        </Snippet>
    </CodeSnippet>
</CodeSnippets>

Installing Snippets

To install the snippets:

  1. Copy/paste each snippet into a dedicated file on your hard-drive, eg propbp.snippet
  2. In Visual Studio, select Tools –> Code Snippets Manager
  3. Click the Import button
  4. Navigate to the location where you put the snippets
  5. Select one or more snippet files.
  6. Click Open and Ok.

To use them, simply type the keyword for the snippet (as defined in the ShortCut element in the snippet) and press the tab key twice.

Note: If you have resharper installed, the default tab/tab keyboard shortcut might not be enabled.

Any feedback is greatly welcomed.

Enjoy.

submit to reddit

Monday, April 17, 2017

DataBinding for Custom Xamarin.Forms controls

In my last post we looked at obtaining a reference to controls from within our ViewModel. While this approach is a good workaround for non-MVVM friendly user controls a better long term strategy is to adapt these controls to support the MVVM features you need. Today's post will take a quick stab at adding binding support to the Map control.

If you follow Xamarin’s walk-through for the Map control it shows pins are added directly to the control from the code-behind. This post will use a more MVVM-friendly approach by adding a few bindable properties to the custom control so that we can manipulate the Map using data in the ViewModels only. We'll design our custom Map to dynamically add and remove pins, and to set focus and center on a specific pin.

Create a Custom Control

Because we’re not changing the behaviour or appearance of the control we simply need to derive from the Map control and Xamarin.Forms will use its default renderer to display the control. If we wanted to change the behavior or layout of the pins, things get a bit more complicated and we’d need to create a custom renderer for each platform. Xamarin has a good tutorial on how to create custom renderers.

Here’s the starting point for our custom map.

namespace XF.CaliburnMicro1.Controls
{
    using Xamarin.Forms;
    using Xamarin.Forms.Maps;
    
    public class CustomMap : Map
    {
    
    }
    
}

Add Bindable Properties

Although the Map control has several bindable properties already (HasScrollEnabled, HasZoomEnabled, IsShowingUser, MapType) it does not support properties for the Pins or currently selected pin. These are easy to add:

public static BindableProperty PinsItemsSourceProperty =
    BindableProperty.Create(
        "PinsItemsSource",
        typeof(IEnumerable<Pin>),
        typeof(CustomMap),
        default(IEnumerable<Pin>),
        propertyChanged: OnPinsItemsSourcePropertyChanged);

public static BindableProperty SelectedItemProperty =
    BindableProperty.Create(
        "SelectedItem",
        typeof(Pin),
        typeof(CustomMap),
        null,
        defaultBindingMode: BindingMode.TwoWay,
        propertyChanged: OnSelectedItemChanged
        );

public Pin SelectedItem
{
    get { return (Pin)GetValue(SelectedItemProperty); }
    set { SetValue(SelectedItemProperty, value); }
}

public IEnumerable<Pin> PinsItemsSource
{
    get { return (IEnumerable<Pin>)GetValue(PinsItemsSourceProperty); }
    set { SetValue(PinsItemsSourceProperty, value); }
}

Add Property Changed Callbacks

Similar to WPF’s DependencyProperties, BindableProperties have the ability to invoke a callback when new values are assigned through data bindings. The callback function for the data-bound pin collection is pretty straight forward — it assigns the new value to a property on the control. The real magic for the collection is that it uses Caliburn.Micro’s BindableCollection<T> – a thread-safe implementation of ObservableCollection<T> – which we use to listen for changes to the collection. Whenever the collection changes we copy the Pin to the Control’s default Pins collection.

private static void OnPinsItemsSourcePropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
    var control = (CustomMap)bindable;

    control.PinsCollection = newValue as IObservableCollection<Pin>;
}

private IObservableCollection<Pin> _collection;

protected IObservableCollection<Pin> PinsCollection
{
    get { return _collection; }
    set
    {
        if (_collection != null)
        {
            _collection.CollectionChanged -= OnObservableCollectionChanged;
        }

        _collection = value;

        if (_collection != null)
        {
            _collection.CollectionChanged += OnObservableCollectionChanged;
        }
    }
}

private void OnObservableCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
    if (e.Action == NotifyCollectionChangedAction.Add)
    {
        foreach (Pin pin in e.NewItems)
        {
            pin.Clicked += OnPinClicked;
            Pins.Add(pin);
        }
    }

    if (e.Action == NotifyCollectionChangedAction.Remove)
    {
        foreach (Pin pin in e.NewItems)
        {
            pin.Clicked -= OnPinClicked;
            Pins.Remove(pin);
        }
    }
}

We’ll also take this time to associate the Pin’s Click event to our SelectedItem property.

private void OnPinClicked(object sender, EventArgs e)
{
    SelectedItem = (Pin)sender;
}

And since we’re setting the SelectedItem, let’s center the view on the selected pin. Because the binding mode of the property is set to TwoWay this callback is invoked from both changes in the control or from the ViewModel.

private static void OnSelectedItemChanged(BindableObject bindable, object oldValue, object newValue)
{
    var map = (CustomMap)bindable;
    var pin = newValue as Pin;
    if (pin != null)
    {
        Distance distance = map.VisibleRegion.Radius;
        MapSpan region = MapSpan.FromCenterAndRadius(pin.Position, distance);
        map.MoveToRegion(region);
    }
}

Putting it all together

Now to see this in action, let’s demonstrate a View with our custom map and a few buttons on it. We’ll wire the buttons to a command that sets the SelectedItem.

<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:maps="clr-namespace:Xamarin.Forms.Maps;assembly=Xamarin.Forms.Maps"
             xmlns:controls="clr-namespace:XF.CaliburnMicro1.Controls"
             x:Class="XF.CaliburnMicro1.Views.Tab3View"            
             >

    <StackLayout VerticalOptions="FillAndExpand">

        <Grid HeightRequest="150">
            <Grid.RowDefinitions>
                <RowDefinition />
                <RowDefinition />
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition />
                <ColumnDefinition />
                <ColumnDefinition />
            </Grid.ColumnDefinitions>

            <Button Grid.Row="0" Grid.Column="0" Command="{Binding SelectPinCommand}" CommandParameter="1" Text="1" />
            <Button Grid.Row="0" Grid.Column="1" Command="{Binding SelectPinCommand}" CommandParameter="2" Text="2" />
            <Button Grid.Row="1" Grid.Column="0" Command="{Binding SelectPinCommand}" CommandParameter="3" Text="3" />
            <Button Grid.Row="1" Grid.Column="1" Command="{Binding SelectPinCommand}" CommandParameter="4" Text="4" />

            <Button Grid.Row="1" Grid.Column="2" Command="{Binding NewPinCommand}" Text="Add Pin" />
        </Grid>

        <controls:CustomMap 
            IsShowingUser="true"
            MapType="Street"
            PinsItemsSource="{Binding Pins}"
            SelectedItem="{Binding SelectedPin}" 
            VerticalOptions="FillAndExpand"
          />
        
    </StackLayout>    
    
</ContentView>

The ViewModel is really quite simple now. We simply populate a list of Pins and change the SelectedPin to trigger changes in the View.

namespace XF.CaliburnMicro1.ViewModels
{
    using Caliburn.Micro;
    using Xamarin.Forms.Maps;
    using XF.CaliburnMicro1.Views;

    public class Tab3ViewModel : BaseScreen
    {
        private Pin _selectedPin;

        public Tab3ViewModel()
        {
            Pins = new BindableCollection<Pin>();

            SelectPinCommand = new DelegateCommand(o =>
            {
                var index = int.Parse(o.ToString());

                SelectedPin = Pins[index - 1];
            });

            NewPinCommand = new DelegateCommand((o) =>
            {
                var position = MapControl.VisibleRegion.Center;
                var pin = Create("New!", position.Latitude, position.Longitude);
                Pins.Add(pin);
                
            });

        }

        public BindableCollection<Pin> Pins { get; protected set; }

        public Pin SelectedPin
        {
            get { return _selectedPin; }
            set { SetField(ref _selectedPin, value); }
        }

        public DelegateCommand NewPinCommand { get; set; }

        public DelegateCommand SelectPinCommand { get; set; }

        protected override void OnActivate()
        {
            base.OnActivate();

            Pins.Clear();

            // top 4 largest cities in north america
            Pins.Add(Create("Mexico City", 19.4326, -99.1332));
            Pins.Add(Create("New York", 40.7128, -74.0059));
            Pins.Add(Create("Los Angeles", 34.0522, -118.2437));
            Pins.Add(Create("Toronto", 43.6532, -79.3832));
        }

        private Pin Create(string label, double lat, double longitude)
        {
            return
                  new Pin
                  {
                      Label = label,
                      Position = new Position(lat, longitude),
                      Type = PinType.Generic
                  };
        }
    }
}

And Voila! Our Map is now populated and driven using data in the ViewModel!

map-android2map-ios

Happy coding.

submit to reddit

Monday, April 10, 2017

Applying MVVM to Difficult UI Elements

It’s worth repeating: the general principle in MVVM is to separate logic from presentation code. This separation makes it easy to swap out presentation elements and unit test our control logic. The generally accepted litmus test for “good mvvm” is the View should be XAML only and all code should be contained in the view-model. Ultimately it leads to more opportunities for code-reuse and higher unit testing code coverage.

However, living up to this ideal can be difficult to achieve, especially when rogue user controls weren’t designed with MVVM in mind. The MapControl is a great example of this – it offers so many complex features (layers, pushpins, custom-shapes, etc) that it would be difficult to produce a simple abstraction.

Caliburn.Micro has a Screen ViewModel that represents important view-lifecycle events. The OnViewAttached method provides an opportunity to obtain a reference to the view and perform logic that you would normally have to do in code-behind. It feels a bit hacky (it sort of is, I’ll show some alternatives in another post) – but it’s a good workaround for difficult scenarios. Keep in mind this is an exception to the rule, something that you might use once in a while.

This recipe uses these ingredients:

  • A named element in the View
  • An interface for the View (optional)
  • Override the OnViewAttached in the ViewModel

The View

We’ll start with a XAML View that uses the Xamarin.Forms Map control. There’s a bit of work to get the Map up and running, so I’ll refer you to Xamarin’s walk-through on how to configure your project. Though most examples from Xamarin show the Map control being used from code-behind, we should be able to do anything they do in those samples from our ViewModel.

In order to access the MapControl programmatically, we have to give it an x:Name.

<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:maps="clr-namespace:Xamarin.Forms.Maps;assembly=Xamarin.Forms.Maps"             
             x:Class="XF.CaliburnMicro1.Views.Tab3View"            
             >

    <Grid VerticalOptions="FillAndExpand">
        
        <maps:Map x:Name="map"
                  IsShowingUser="True"
                  MapType="Street"
                  />
    </Grid>    
    
</ContentView>

Interface for the View

This step is optional and is largely because I don’t want to couple the View explicitly in the ViewModel. By defining an interface, I can use this technique with different Views. For example, you might subclass the Map control and use it different areas of your application. The interface approach makes it easy to access the control regardless of where it’s used in my app.

namespace XF.CaliburnMicro1.Views
{
    using Xamarin.Forms.Maps;

    public interface IMapAware
    {
        Map GetMap();
    }
}

Next we simply implement the interface on the View and return our named element.

namespace XF.CaliburnMicro1.Views
{
    using Xamarin.Forms;
    using Xamarin.Forms.Maps;

    public partial class Tab3View : ContentView, IMapAware
    {
        public Tab3View()
        {
            InitializeComponent();
        }

        public Map GetMap()
        {
            return this.map;
        }
    }
}

Access the Control from the ViewModel

The last step here assumes that we’re backing our Xamarin.Form Pages with ViewModels that derive from Caliburn.Micro’s Screen class. To access the control from the ViewModel, we override the OnViewAttached method and either cast the argument to our View or to the interface mentioned above.

In this example, I’m assigning the Control to a property on the ViewModel so that I can centralize initialization logic, such as subscribing to events and setting default properties.

namespace XF.CaliburnMicro1.ViewModels
{
   using Caliburn.Micro;
   using Xamarin.Forms.Maps;
   
   public class Tab3ViewModel : Screen
   {
      internal Map MapControl
      {
         get { return _map; }
         set
         {
            if (_map != null)
            {
               // unregister events
            }

            _map = value;

            if (_map != null)
            {
               // wire-up events
            }
         }
      }

      protected override void OnViewAttached(object view, object context)
      {
         var mapView = view as IMapAware;
         if (mapView != null)
         {
            MapControl = mapView.GetMap();
         }
      }

      protected override void OnActivate()
      {
         base.OnActivate();

         CenterMap();
      }
      
      internal void CenterMap()
      {
         var mapSpan = MapSpan.FromCenterAndRadius(
            new Position(43.6532, -79.3832),
            Distance.FromKilometers(10));
         MapControl.MoveToRegion(mapSpan);
      }
      
   }
   
}

Sweet. Now we can add our Pins to the Map from the ViewModel. In my next post, we'll look at a solution to apply our Pins using MVVM so that we don't need to manipulate the Map directly.

Happy coding.