Showing posts with label .net. Show all posts
Showing posts with label .net. Show all posts

Monday, February 10, 2020

Challenges with Parallel Tests on Azure DevOps

As I wrote about last week, Adventures in Code Spelunking, relentlessly digging into problems can be a time-consuming but rewarding task.

That post centers around a tweet I made while I was struggling with an issue with VSTest on my Azure DevOps Pipeline. I'm feel I'm doing something interesting here: I've associated my automated tests to my test cases and I'm asking the VSTest task to run all the tests in the Plan; this is considerably different than just running the tests that are contained in the test assemblies. The challenge at the time was that the test runner wasn't finding any of my tests. My spelunking exercise revealed that the runner required an array of test suites despite the fact that the user interface restricts you to pick only one. I modified my yaml pipeline to contain a comma-delimited list of suites. Done!

Next challenge, unlocked!

Unfortunately, this would turn out to be a short victory, as I quickly discovered that although the VSTest task was able to find the test cases, the test run would simply hang with no meaningful insight as to why.

[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.4.1 (64-bit .NET Core 3.1.1)
[xUnit.net 00:00:00.52]   Discovering: MyTests
[xUnit.net 00:00:00.57]   Discovered: MyTests
[xUnit.net 00:00:00.57]   Starting: MyTests
-> Loading plugin D:\a\1\a\SpecFlow.Console.FunctionalTests\TechTalk.SpecFlow.xUnit.SpecFlowPlugin.dll
-> Using default config

So, on a wild hunch I changed my test plan so that only a single test case was automated, and it worked. What gives?

Is it me, or you? (it’s probably you)

The tests work great on my local machine, so it’s easy to fall into a trap that the problem isn’t me. But to truly understand the problem is to be able to recreate it locally. And to do that, I’d need to strip away all the unique elements until I had the most basic setup.

My first assumption was that it might actually be the VSTest runner -- a possible issue with the “Run Test Plan” option I was using. So I modified my build pipeline to just run my unit tests like normal regression tests. And surprisingly, the results were the same. So, maybe it’s my tests.

Under a hunch that I might have a threading deadlock somewhere in my tests, I hunted through my solution looking for rogue asynchronous methods and notorious deadlock maker Task.Result. There were none that I could see. So, maybe there’s a mismatch in the environment setup somehow?

Sure enough, I had some mismatches. My test runner from the command-prompt was an old version. The server build agent was using a different version of the test framework than what I had referenced in my project. After upgrading nuget packages, Visual Studio versions and fixing the pipeline to exactly match my environment – I still was unable to reproduce the problem locally.

I have a fever, and the only prescription is more logging

Well, if it’s a deadlock in my code, maybe I can introduce some logging into my tests to put a spotlight on the issue. After some initial futzing around (I’m amazing futzing wasn’t caught by spellcheck, btw), I was unable to get any of these log messages to appear in my output. Maybe xUnit has a setting for this?

Turns out, xUnit has a great logging capability but requires a the magical presence of the xunit.runner.json file in the working directory.

{
  "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json",
  "diagnosticMessages": true
}

The presence of this file reveals this simple truth:

[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.4.1 (64-bit .NET Core 3.1.1)
[xUnit.net 00:00:00.52]   Discovering: MyTests (method display = ClassAndMethod, method display options = None)
[xUnit.net 00:00:00.57]   Discovered: MyTests (found 10 test cases)
[xUnit.net 00:00:00.57]   Starting: MyTests (parallel test collection = on, max threads = 8)
-> Loading plugin D:\a\1\a\SpecFlow.Console.FunctionalTests\TechTalk.SpecFlow.xUnit.SpecFlowPlugin.dll
-> Using default config

And when compared to the server:

[xUnit.net 00:00:00.00] xUnit.net VSTest Adapter v2.4.1 (64-bit .NET Core 3.1.1)
[xUnit.net 00:00:00.52]   Discovering: MyTests (method display = ClassAndMethod, method display options = None)
[xUnit.net 00:00:00.57]   Discovered: MyTests (found 10 test cases)
[xUnit.net 00:00:00.57]   Starting: MyTests (parallel test collection = on, max threads = 2)
-> Loading plugin D:\a\1\a\SpecFlow.Console.FunctionalTests\TechTalk.SpecFlow.xUnit.SpecFlowPlugin.dll
-> Using default config

Yes, Virginia, there is a thread contention problem

The build agent on the server has only 2 virtual CPUs allocated and both executing tests are likely trying to spawn additional threads to perform the asynchronous operations. By setting the maxParallelThreads to “2” I am able to completely reproduce the problem from the server.

I can disable parallel execution in the tests by adding the following to the assembly:

[assembly: CollectionBehavior(DisableTestParallelization = true)]

…or by disabling parallel execution in the xunit.runner.json:

{
  "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json",
  "diagnosticMessages": true,
  "parallelizeTestCollections": false
}

submit to reddit

Thursday, October 19, 2017

Xamarin.Forms with Caliburn.Micro walk-through

As of this morning, I posted a new version of my Xamarin.Forms with Caliburn.Micro Starter Kit on the Visual Studio Marketplace.

This video provides a quick walk-through of using the template.

submit to reddit

Monday, October 16, 2017

Jump start your next project with Xamarin.Forms Caliburn.Micro Starter Kit

Hey all! I’ve bundled my walkthrough of setting up a Xamarin.Forms to use Caliburn.Micro for Android, iOS and UWP into a Visual Studio Project Template and made it available in the Visual Studio Extensions Gallery

You can download it directly here, or from within Visual Studio: Tools –> Extensions and Updates –> Online.

Update 10/18/2017:

  • I had to republish the package as a “Tool” because it includes a few code snippets. The VS Gallery doesn’t allow you to change the classification of the VSIX, so I had to republish under a new identifier. You’ll need to uninstall and reinstall the new template.

image

As a multi-project, it’s very straight forward to use, simply choose File –> New and select “Xamarin.Forms with Caliburn.Micro”

image

Will create a project with the following structure:

image

Which, when run (on your platform of choosing) looks like this screenshot below. This is right where we left off from my walk-through earlier this year and is a great starting point for prototyping or building your next app.

image

Known Issues

  • Windows does not automatically create a signing key and identity for your app. Be sure to edit the UWP manifest and associate with your signing identity.

The source code for the starter kit can be found here. Let me know what you think!

submit to reddit

Tuesday, October 10, 2017

Bundle your Visual Studio Solution as a Multi-Project Template

Earlier this year I provided a walkthrough of setting up a Xamarin.Forms project that leveraged Caliburn.Micro for Android, iOS and UWP. I had big plans for extracting the contents of that walkthrough and providing it as a NuGet package. Plans changed however, and I’ve decided to package the entire solution as a Multi-Project Template and provide it as an add-on to Visual Studio (VSIX). This post introduces provides a walk-through on how to create multi-project templates.

Wait, why not NuGet?

First off, as an aside, let’s go back and look what I wanted to do. I wanted to provide a starter-kit of files that would jump start your efforts and allow you to modify my provided files as you see fit. As a NuGet package, I can deliver these files to any project simply by adding these loose code files in the content folder of the NuGet package. Two things that are really awesome about this: the code files can be treated as source code transforms by changing their extension to *.pp, and through platform targeting I could deliver different content files per platform (Xamarin.iOS10, Xamarin.Android10, uap10.0, etc). With this approach, you would simply create a new Xamarin.Forms project then add the NuGet package to all projects. Bam. Easy.

But there are a few problems with this approach:

  • Existing files. My NuGet package would certainly be replacing existing files in your solution. I’d want to overwrite key parts of the initial template (App.xaml, AppDelegate, Activity, etc) and in some cases delete files (MainPage.xaml). Technically, I can overcome these side-effects by modifying the project through a NuGet install script (install.ps1). However, you would be prompted during the install about the replacements and if you clicked ‘No’ when prompted to replace these files… my template wouldn’t work.
  • Delivering Updates. This is the funny thing about this approach -- it is really intended as a one time deal. You would add the starter files to your project and then begin to modify and extend to your hearts’ content. However, as the package author, no doubt I would find an issue or improvement for the package and publish it. If you were to update the package, it would repeat its initialization process and nuke your customizations. I would prefer not to see you when you’re angry.
  • Not guaranteed. Lastly, you could try and add the NuGet package to only one of your projects, or to a library that isn’t intended as a Xamarin.Forms project.

Above all else, the NuGet documentation clearly states that these files should be treated immutable and not intended to be modified by the consuming project. And since the best place to add the package is immediately after you create the project using a Visual Studio Template, why not just make a Template?

Creating a Multi-Project Template

While Multi-Project Templates have been around for a while, their tooling has improved considerably over the last few releases of Visual Studio. Although there isn’t a feature to export an entire solution as a multi-project, they conceptually work the same way as creating a single project template and then tweaking it slightly.

There are two ways to create a Project Template. The first and easiest is simply to select Project –> Export Template. The wizard that appears will prompt you for a Project and places your template in the My Exported Templates folder.

The second approach requires you to install the Visual Studio SDK, which can be found as an option in the initial installer. When you have the SDK installed, you can create a Project Template as an item in your solution. This project includes the necessary vstemplate files and produces the packaged template every time you build.

image

Effectively, a Project Template is just a zip file with a .vstemplate file in it. A Multi-Project Template has a single .vstemplate that points to templates in subfolders. Here’s how I created mine:

1. Create a Project Template project

Using the Visual Studio SDK, I created a Project Template project to my solution and modified the VSTemplate file with the appropriate details:

<VSTemplate Version="2.0.0" Type="ProjectGroup"
    xmlns="http://schemas.microsoft.com/developer/vstemplate/2005">
  <TemplateData>
    <Name>Xamarin.Forms with Caliburn.Micro</Name>
    <Description>Xamarin.Forms project with PCL library.</Description>
    <ProjectType>CSharp</ProjectType>
    <Icon>_icon.ico</Icon>
    <DefaultName>App</DefaultName>
    <ProvideDefaultName>true</ProvideDefaultName>
    <CreateNewFolder>true</CreateNewFolder>
    <RequiredFrameworkVersion>2.0</RequiredFrameworkVersion>
    <SortOrder>1000</SortOrder>
    <TemplateID>Your ID HERE</TemplateID>
  </TemplateData>
  <TemplateContent/>
</VSTemplate>


2. Export Projects and Add to the Project Template project

Next, simply export all the projects in your solution that you want to include in your template. The Project –> Export Template dialog looks like this:

image

Once you’ve exported the projects as templates take each of the zip files and extract them into a subfolder of your Template Project. Then, in Visual Studio, include these extracted subfolders as part of the project. Note that Visual Studio will assign a default Action for each file, so code files will be set to Compile, images will be set as EmbeddedResource, etc. You’ll have to go through each of these files and change the default action to Content, copy if newer. It’s a pain, and I found it easier to unload the project and manually edit the csproj file directly.

3. Configure the Template to include the embedded Projects

Now that we have the embedded projects included in the output, we need to modify the template to point to these embedded templates. Visual Studio has a set of reserved keywords that can be used in the vstemplate and code transforms; $safeprojectname$ is a reserved keyword that represents the name of the current project. My vstemplate names the referenced templates after the name that was provided by the user:

<VSTemplate Version="2.0.0" Type="ProjectGroup"
    xmlns="http://schemas.microsoft.com/developer/vstemplate/2005">
  <TemplateData>
    ...
  </TemplateData>
  <TemplateContent>
    <ProjectCollection>
      <ProjectTemplateLink ProjectName="$safeprojectname$" CopyParameters="true">XF\MyTemplate.vstemplate</ProjectTemplateLink>
      <ProjectTemplateLink ProjectName="$safeprojectname$.Android" CopyParameters="true">XF.Android\MyTemplate.vstemplate</ProjectTemplateLink>
      <ProjectTemplateLink ProjectName="$safeprojectname$.UWP" CopyParameters="true">XF.UWP\MyTemplate.vstemplate</ProjectTemplateLink>
      <ProjectTemplateLink ProjectName="$safeprojectname$.iOS" CopyParameters="true">XF.iOS\MyTemplate.vstemplate</ProjectTemplateLink>
    </ProjectCollection>
  </TemplateContent>  
</VSTemplate>

If the ProjectName is omitted, it will use the name within the embedded template.

4. Fix Project References

To ensure the project compiles, we must fix the project references to the PCL library in the iOS, Android and UWP projects. Here we leverage an interesting feature of Multi-Project templates – Visual Studio provides special reserved keywords for accessing properties of the root template project. In this case, we can reference the safeprojectname of the root project using the $ext_safeprojectname$ reserved keyword. And because project references use a GUID to refer to the referenced project, we can provide the PCL project with a GUID that will be known to all the child projects – in this case, we can use $ext_guid1$.

The <ProjectGuid> element in the PCL Project must be configured to use the shared GUID:

<PropertyGroup>
  <MinimumVisualStudioVersion>11.0</MinimumVisualStudioVersion>
  <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
  <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
  <ProjectGuid>{$ext_guid1$}</ProjectGuid>
  <OutputType>Library</OutputType>
  <AppDesignerFolder>Properties</AppDesignerFolder>
  <RootNamespace>$safeprojectname$</RootNamespace>
  <AssemblyName>$safeprojectname$</AssemblyName>
  <FileAlignment>512</FileAlignment>
  <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
  <TargetFrameworkProfile>Profile259</TargetFrameworkProfile>
  <ProjectTypeGuids>{786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
  <NuGetPackageImportStamp>
  </NuGetPackageImportStamp>
</PropertyGroup>

In the projects that reference the PCL, the path to the project, project GUID and Name must be also be modified:

<ItemGroup>
  <ProjectReference Include="..\$ext_safeprojectname$\$ext_safeprojectname$.csproj">
    <Project>{$ext_guid1$}</Project>
    <Name>$ext_projectname$</Name>
  </ProjectReference>
</ItemGroup>

5. Fix-ups

Lastly, there will be some other fix-ups you will need to apply. These are things like original project names that appear in manifest files, etc. The templating engine can make changes to any type of file, but you may need to verify that these files have the ReplaceParameters attribute set to True in the .vstemplate file.

Build and Deploy!

With this in place, you can simply compile the Project Template and copy the zip to ProjectTemplates folder. Optionally, you can add a VSIX project to the solution that you can use to bundle our Project Template as an installer that you can distribute to users via the Visual Studio Extensions Gallery.

Happy coding!

submit to reddit

Thursday, September 28, 2017

Unit testing Xamarin.Forms Behaviors

So in my last post, I outlined how I’m unit testing my Xamarin.Forms projects. Today, I want to highlight a practical example of unit testing a Behavior.

Let’s take a simple behavior that prevents item selection in a ListView:

public class DisableListViewSelection : Behavior<ListView>
{
    private ListView _attached;

    protected override void OnAttachedTo(ListView bindable)
    {
        _attached = bindable;

        if (_attached != null)
        {
            _attached.ItemSelected += Bindable_ItemSelected;
        }
    }


    protected override void OnDetachingFrom(ListView bindable)
    {
        if (_attached != null)
        {
            _attached.ItemSelected -= Bindable_ItemSelected;
        }
    }

    private void Bindable_ItemSelected(object sender, SelectedItemChangedEventArgs e)
    {
        _attached.SelectedItem = null;
    }
}

Unit testing the behavior should be straight forward but there are a few gotchas.

The first concern is that we're testing a visual that requires Xamarin.Forms to be initialized using Xamarin.Forms.Forms.Init();. This is easily addressed using the Xamarin.Forms.Mocks nuget package I mentioned in my last post.

The second concern is that the Behavior<T> implementation explicitly implements the IAttachedObject interface which is marked as internal. We can address this with some Reflection hackery.

I’ve addressed both concerns with the following base test fixture:

public abstract class BaseBehaviorTests<TSubjectBehavior, TTargetElement> : INotifyPropertyChanged
    where TSubjectBehavior : Behavior<TTargetElement>, new() 
    where TTargetElement : BindableObject, new()
{
    private BindingFlags _bindingFlags = BindingFlags.NonPublic | BindingFlags.Instance;

    public TTargetElement ContainingElement { get; set; }
    public TSubjectBehavior Subject { get; set; }

    public event PropertyChangedEventHandler PropertyChanged;

    public virtual void Setup()
    {
        Xamarin.Forms.Mocks.MockForms.Init();

        Subject = new TSubjectBehavior();
        ContainingElement = new TTargetElement();
    }

    protected virtual void Attach()
    {
        Subject.GetType().GetMethod("Xamarin.Forms.IAttachedObject.AttachTo", _bindingFlags).Invoke(Subject, new object[] { ContainingElement });
    }

    protected virtual void Detach()
    {
        Subject.GetType().GetMethod("Xamarin.Forms.IAttachedObject.DetachFrom", _bindingFlags).Invoke(Subject, new object[] { ContainingElement });
    }

    protected void NotifyPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Take a quick peek at the Attach/Detach methods. The IAttachedObject.AttachTo method is explicitly implemented on the class so we have to use the full namespace of the method to resolve it. If it was implicitly implemented, we could simply use "AttachTo".

Now that we have this test fixture capability, writing a test for our DisableListViewSelectionBehavior is dead simple:

[TestClass]
public class DisableListViewSelectionBehaviorTests : BaseBehaviorTests<DisableListViewSelection, ListView>
{
    List<object> _list = new List<object>();

    [TestInitialize]
    public override void Setup()
    {
        _list.Add(new object());

        base.Setup();
    }

    [TestMethod]
    public void WhenSelectingItem_AndAttachedToBehavior_ShouldUnselectedItem()
    {
	// arrange
	Attach();
        ContainingElement.ItemsSource = _list;

	// act
        ContainingElement.SelectedItem = _list.First();

	// assert
        ContainingElement.SelectedItem.ShouldBeNull();
    }

    [TestMethod]
    public void WhenSelectingItem_AndDetachedFromBehavior_ShouldKeepSelectedItem()
    {
	// arrange
        Detach();
        ContainingElement.ItemsSource = _list;

	// act
        ContainingElement.SelectedItem = _list.First();

	// assert
        ContainingElement.SelectedItem.ShouldNotBeNull();
    }
}


Well, that's all for now. My next post will look at behaviors with data binding.

Happy coding!

submit to reddit

Wednesday, September 27, 2017

Unit testing Xamarin.Forms

As a TDD Evangelist, I’m well aware of the dichotomy between desired and actual testing practices. It can be hard to write tests when we’re rapid prototyping, and we can convince ourselves that we’re writing testable code, but at some point, you need to establish some testing practices before things scale beyond your reach. This week I’ve been looking at some code where I supported the engineering team but unit testing wasn’t our top priority. I’m glad that I helped shape the code with testability in mind, but now that I’m writing unit tests for the code I’m discovering fallacies in our thinking and areas that are proving difficult to test.

Still, I’ve managed to go from 0% to 50% code coverage in about 10 days which is promising. I’m also hopeful that there’ll be lots more code in testable areas so this number should only trend upward. These last few days I’ve been looking at squeezing out a few extra tests for some of the custom behaviors and I quickly discovered that testing code for visual elements is challenging. Here’s a breakdown of how I’m approaching testing for Xamarin.Forms.

Project Setup

First off, since 90% of my logic resides within the shared PCL layer my focus is on writing unit tests for ViewModels, Services and Behaviors. Some services are platform specific and for those I will likely need to test using Xamarin.iOS or Xamarin.Android, but for the PCL layer I’m using MSTest and .NET 4.6. The choice for using a .NET library instead of Xamarin.iOS or Xamarin.Android is largely for convience and speed of running the tests as they don’t require an emulator or device, and I’m also targetting UWP so I have to compile on a windows machine regardless. I also want to leverage a mocking framework like Moq which won’t work correctly on Mono.

Mocking

For unit testing my ViewModels, I wrote a simple extension to Caliburn.Micro’s dependency container that can automatically fill my viewmodels with fake dependencies.

public class TestContainer : SimpleContainer
{
    public T CreateSubject<T>()
    {
        Type targetType = typeof(T);

        var greedyConstructor = targetType
            .GetConstructors()
            .OrderByDescending(i => i.GetParameters().Length)
            .FirstOrDefault();

        foreach(var arg in greedyConstructor.GetParameters())
        {
            // handle IEnumerable<T> in constructor
            if (typeof(IEnumerable).IsAssignableFrom(arg.ParameterType))
            {
                var genericType = arg.ParameterType.GenericTypeArguments[0];

                // ensure we have at least one item in the array
                if (!HasHandler(genericType, null))
                {
                    CreateAndInsertMock(genericType);
                }
            }
            else
            {
                if (!HasHandler(arg.ParameterType, null))
                {
                    CreateAndInsertMock(arg.ParameterType);
                }
            }
        }

        this.PerRequest<T>();

        return this.GetInstance<T>();
    }
    
    public Mock<T> GetMock<T>() where T : class
    {
        var obj = this.GetInstance<T>();
        if (obj == null)
        {
            throw new InvalidOperationException("Mock is not directly used by the subject.");
        }

        return Mock.Get(obj);
    }

    public Mock<T> GetMock<T>(int index) where T : class
    {
        var instances = this.GetAllInstances<T>();

        return Mock.Get(instances.ToArray()[index]);
    }

    public Mock<T> AddMock<T>(params Type[] interfaces) where T : class
    {
        var mock = new Mock<T>();
        mock.SetupAllProperties();

        if (interfaces.Length > 0)
        {
            var asMethodInfo = mock.GetType().GetMethod("As");
            foreach(var def in interfaces)
            {
                var method = asMethodInfo.MakeGenericMethod(def);
                method.Invoke(mock, null);
            }
        }

        var instance = mock.Object;

        RegisterInstance(typeof(T), null, instance);
        foreach (var def in interfaces)
        {
            RegisterHandler(def, null, container => instance);
        }            

        return mock;
    }        

    private Mock CreateAndInsertMock(Type targetType)
    {
        var method = this.GetType().GetMethod("AddMock").MakeGenericMethod(targetType);
        return (Mock)method.Invoke(this, new object[] { new Type[] { } } );
    }
}

This coupled with a base test fixture really helped to get my viewmodels under the test microscope quickly.

public abstract class BaseViewModelTest<T> where T : BaseScreen
{
    private TestContainer _container;    

    protected T Subject { get; set; }

    public virtual void Setup()
    {
        _container = new TestContainer();
        Subject = _container.CreateSubject<T>();
    }

    protected Mock<TDependency> Get<TDependency>() where TDependency : class
    {
        reutnr _container.GetMock<TDependency>();
    }

    protected Mock<TDependency> Set<TDependency>() where TDependency : class
    {
        return _container.AddMock<TDependency>();
    }

    protected void Activate()
    {
        var activatable = Subject as Caliburn.Micro.IActivate;
        if (activatable != null)
        {
            activatable.Activate();
        }
    }
}

[TestClass]
public class HomeScreenTests : BaseViewModelTest<HomeScreenViewModel>
{
    [TestInitialize]
    public override void Setup()
    {
        base.Setup();

        // additional setup
    }

    // Tests...
}

Testing UI Elements

As I started writing unit tests for controls and behaviors, I realized that any Xamarin.Forms UI element was going to require the Xamarin.Forms.Forms.Init() method to be invoked, which would not work for my test project. Further investigation revealed that most of the plumbing within Xamarin is marked as internal or with the [EditorBrowsable(EditorBrowsableState.Never)] which makes it impossible for us to initialize with mocks …externally. The only way to get at these internals is through the InternalsVisibleTo attribute.

Fortunately, Xamarin MVP Jon Peppers has discovered the same issue and realized a small security flaw in Xamarin’s usage of [InternalsVisibleTo]. Since their usage doesn’t require the use of a public key, anyone can access these internals if they name their assembly a certain way. Jon has published a nuget package that contains Xamarin.Forms.Core.UnitTests.dll assembly. His dummy versions act as a great stand-in for bypassing the platform dependencies allowing us to write tests for simple functionality instead of platform behavior.

My next few posts will cover a few practical examples.

submit to reddit

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, 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 03, 2017

Displaying a Xamarin.Forms ActionSheet using MVVM

When it comes to mobile applications, there are many different ways to prompt a user for input. Xamarin.Forms has adopted a technique originally introduced in iOS called an ActionSheet that can prompt the user for one or many options.

While Xamarin.iOS provides the UIAlertController to display Popups and ActionSheets, the only way to present the ActionSheet in Xamarin.Forms is through the DisplayActionSheet method on the Page object. If we want to present this dialog to our users using MVVM, accessing the Page object from the code-behind would be violating one of the MVVM best-practices, so we’d need to find another way that’s more “MVVM Friendly”. I’ve had a lot of success with the following approach. Maybe you will, too.

What’s an ActionSheet?

Before we dive into the MVVM component, let’s take a closer look at the ActionSheet. An ActionSheet prompts the user to choose a selection from a list but also provides two specific options: Cancel and Destruction.  The “Cancel” option is obvious, but perhaps the “Destruction” option may not. In iOS, this term represents a destructive operation that is highlighted in red. For example, an actionsheet shown to users during a photo editing session may show a list of file sizes (“Small”, “Medium”, “Large”), a “Cancel” button and a destructive operation, “Discard Changes”.

Options, Options, Options…

To show the ActionSheet using bindings from our ViewModel, we have a few options:

  1. Implement a custom control. We could implement a custom control to house our logic to launch the ActionSheet, but this feels a bit awkward, especially since it wouldn’t actually render anything. We’d also run into issues about where to put the Control in the logical tree.
  2. Custom Behaviour. This seems like the best approach, but the way behaviours are implemented in Xamarin.Forms isn’t exactly how they’re done in WPF, and I discovered first hand a few issues where bindings were being fired multiple times, etc. Which led me to…
  3. Attached Properties. Perhaps the precursor to Behaviors, Attached Properties provide us with a mechanism to store state in the visual elements and then define callbacks for when those values change. This technique isn’t as clean as I’d like but it works really well.

Show me the Codes

As mentioned above, my preferred approach to show the ActionSheet works on this basic principle:

  • Bind some data that we want to show using some Attached Properties,
  • Trigger the ActionSheet when one of the BindableProperty changes,
  • Use the VisualTreeHelper mentioned in my last post to find the Page element,
  • Display the ActionSheet using the Page.DisplayActionSheet method.

We’ll expose the following properties:

  • Parameters: rather than binding all the various display values as separate values, we bundle them up into a single class (see ActionSheetParameters below)
  • IsVisible: the BindableProperty that will be used to trigger the alert.
  • Result: the BindableProperty that will contain the user’s selection.
  • ResultCommand: an alternative to using the Result property if you want to be notified by Command when the user selects a value.

The end result looks like this:


namespace XF.CaliburnMicro1.Controls
{
    using System.Windows.Input;
    using Xamarin.Forms;

    public class ActionSheet
    {
        #region Parameters
        public static BindableProperty ParametersProperty =
            BindableProperty.CreateAttached(
                "Parameters", 
                typeof(ActionSheetParameters), 
                typeof(ActionSheet), 
                default(ActionSheetParameters));

        public static ActionSheetParameters GetParameters(BindableObject bindable)
        {
            return (ActionSheetParameters)bindable.GetValue(ParametersProperty);
        }

        public static void SetParameters(BindableObject bindable, ActionSheetParameters value)
        {
            bindable.SetValue(ParametersProperty, value);
        }
        #endregion

        #region Result
        public static BindableProperty ResultProperty =
            BindableProperty.CreateAttached(
                "Result", 
                typeof(string), 
                typeof(ActionSheet), 
                default(string), 
                defaultBindingMode: BindingMode.TwoWay);

        public static string GetResult(BindableObject bindable)
        {
            return (string)bindable.GetValue(ResultProperty);
        }

        public static void SetResult(BindableObject bindable, string value)
        {
            bindable.SetValue(ResultProperty, value);
        }
        #endregion

        #region Command
        public static BindableProperty CommandProperty =
            BindableProperty.CreateAttached(
                "Command",
                typeof(ICommand),
                typeof(ActionSheet),
                null);

        public static ICommand GetCommand(BindableObject bindable)
        {
            return (ICommand)bindable.GetValue(CommandProperty);
        }

        public static void SetCommand(BindableObject bindable, string value)
        {
            bindable.SetValue(CommandProperty, value);
        } 
        #endregion

        #region IsVisible
        public static BindableProperty IsVisibleProperty =
           BindableProperty.CreateAttached(
               "IsVisible", 
               typeof(bool), 
               typeof(ActionSheet), 
               default(bool), 
               propertyChanged: OnShowDialog, 
               defaultBindingMode: BindingMode.TwoWay);

        public static bool GetIsVisible(BindableObject bindable)
        {
            return (bool)bindable.GetValue(IsVisibleProperty);
        }

        public static void SetIsVisible(BindableObject bindable, bool value)
        {
            bindable.SetValue(IsVisibleProperty, value);
        }
        #endregion

        private static async void OnShowDialog(BindableObject bindable, object oldValue, object newValue)
        {
            bool showAlert = (bool)newValue;
            if (showAlert)
            {
                var page = VisualTreeHelper.GetParent<Page>((Element)bindable);

                ActionSheetParameters args = GetParameters(bindable);
                
                if (page != null && args != null)
                {
                    string result = await page.DisplayActionSheet(args.Title, args.Cancel, args.Destruction, args.Buttons);

                    SetResult(bindable, result); // pass result back to binding

                    // pas result back to viewmodel using command
                    ICommand command = GetCommand(bindable);
                    if (result != null && command != null)
                    {
                        command.Execute(result);
                    }

                    SetIsVisible(bindable, false); // reset the dialog
                }
            }
        }
    }

    public class ActionSheetParameters
    {
        /// <summary>
        /// Action sheet title
        /// </summary>
        public string Title { get; set; }

        /// <summary>
        /// Cancel button title
        /// </summary>
        public string Cancel { get; set; }

        /// <summary>
        /// Destructive action title
        /// </summary>
        public string Destruction { get; set; }

        /// <summary>
        /// List of Buttons
        /// </summary>
        public string[] Buttons { get; set; }
    }
}

From the above, the work is done when the IsVisible property changes. Our ViewModel would look like this:

public class ExampleViewModel : Screen
{
    private bool _showDialog;
    private string _result;
    private ActionSheetParameters _parameters;

    public ExampleViewModel()
    {
        ShowDialogCommand = new DelegateCommand((o) =>
        {
            DialogParameters = new ActionSheetParameters
            {
                Title = "Choose your option wisely",
                Cancel = "Cancel",
                Destruction = "Self Destruct",
                Buttons = new[] { "One", "Two", "Red", "Blue" }
            };

            ShowDialog = true;
        });
    }

    public bool ShowDialog
    {
        get { return _showDialog; }
        set { SetField(ref _showDialog, value); }
    }

    public string Result
    {
        get { return _result; }
        set { SetField(ref _result, value); }
    }

    public ActionSheetParameters DialogParameters
    {
        get { return _parameters; }
        set { SetField(ref _parameters, value); }
    }

    public DelegateCommand ShowDialogCommand
    {
        get;
        protected set;
    }

    protected void SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
    {
        if (!Object.Equals(field, value))
        {
            field = value;
            NotifyOfPropertyChange(propertyName);
        }
    }
}

And the corresponding View:

<?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:controls="clr-namespace:XF.CaliburnMicro1.Controls"
             x:Class="XF.CaliburnMicro1.Views.Tab2View"
             
             controls:ActionSheet.Parameters="{Binding DialogParameters}"
             controls:ActionSheet.Result="{Binding Result}"
             controls:ActionSheet.IsVisible="{Binding ShowDialog}"
             >

    <StackLayout>
        <Button Text="Show Dialog" Command="{Binding ShowDialogCommand}" />
        <Label Text="{Binding Result}" />
    </StackLayout>    
    
</ContentView>

This technique works well for both iOS and Android.

Happy coding.

submit to reddit

Monday, March 13, 2017

Applying MVVM to Xamarin.Form’s TabbedPage (Updated)

My last post showed how to setup a TabbedPage with separate View/ViewModels for each tab using Caliburn.Micro. The post used a DataTemplateSelector to resolve the tab content which isn’t the preferred technique when working with Caliburn.Micro. Today we’ll use a strategy that is more aligned to Caliburn’s philosophy and is a tiny bit more extensible.

As per the previous post, we used a DataTemplateSelector because the default TabbedPage.ItemTemplate expects a ContentPage and we wanted to use existing ContentPage items as tabs. In reality, this was a situation that occurred because our team had originally developed these pages independently and then wanted to consolidate them into a TabbedPage after the fact. If you don’t plan on navigating to these pages outside of the TabbedPage, we can build up our ContentPage inside the DataTemplate and use Caliburn.Micro’s binding syntax. This approach will require us to change our existing ContentPage(s) into ContentView(s) and since we depended on the Page objects to provide us with Title and Icon information, we’ll need to push this information into our ViewModels.

Modify ViewModels

Let’s introduce a simple interface for our ViewModels that will be tabs in our TabbedPage:


public interface ITabViewModel
{
   string Title { get; }
   string Icon { get; }
   int SortOrder { get; }
}

The changes to the ViewModel are pretty trivial. We simply implement the ITabViewModel interface:


public class Tab1ViewModel : Screen, ITabViewModel
{
   public Tab1ViewModel()
   {
      Tab1Content = "Tab 1 Content";
   }

   public string Tab1Content { get; set; }

   public string Icon => "Tab1.png";

   public int SortOrder => 0;

   public string Title => "Tab1";
}

public class Tab2ViewModel : Screen, ITabViewModel
{
   public Tab2ViewModel()
   {
      Tab2Content = "Tab 2 Content";
   }

   public string Tab2Content { get; set; }

   public string Icon => "Tab2.png";

   public int SortOrder => 0;

   public string Title => "Tab 2";
}

Next, we register our ViewModels in the App using the ITabViewModel signature:


public class App : FormsApplication
{
   private readonly SimpleContainer container;

   public App(SimpleContainer container)
   {
      this.container = container;

      // TODO: Register additional viewmodels and services
      container
         .PerRequest<Main2ViewModel>()
         .PerRequest<ITabViewModel,Tab1ViewModel>()
         .PerRequest<ITabViewModel,Tab2ViewModel>()
         ;

      Initialize();

      DisplayRootViewFor<Main2ViewModel>();
   }
 
   // snip...
}

Lastly, we can change our Screen Conductor to be less coupled to the specific ViewModels:


public class Main2ViewModel : Conductor<Screen>.Collection.OneActive
{
   public Main2ViewModel(IEnumerable<ITabViewModel> tabs)
   {
      if (tabs.Any())
      {
         foreach(var tab in tabs.OrderBy(i => i.SortOrder))
         {
            Items.Add((Screen)tab);
         }

         ActivateItem(Items[0]);
      }
   }
}

Modify Views

The changes to the view are also very trivial. We’re simply changing them from ContentPage to ContentView. There are a few attributes that aren’t available (Title, Icon) so we’ll simply remove them if present.
And then finally, we can remove our DataTemplateSelector and use the View.Model attached property to resolve our ContentView:

Main2View.xaml

<?xml version="1.0" encoding="UTF-8"?>
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms" 
            xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
            xmlns:cm="clr-namespace:Caliburn.Micro.Xamarin.Forms;assembly=Caliburn.Micro.Platform.Xamarin.Forms"
            x:Class="XF.CaliburnMicro1.Views.Main2View"
            ItemsSource="{Binding Items}"
            SelectedItem="{Binding ActiveItem}"
            >
    <TabbedPage.ItemTemplate>
        <DataTemplate>
            <ContentPage Title="{Binding Title}" Icon="{Binding Icon}">
                <ContentView cm:View.Model="{Binding}" />
            </ContentPage>
        </DataTemplate>
    </TabbedPage.ItemTemplate>
</TabbedPage>

Easy peasy.

Happy coding.

submit to reddit

Sunday, March 05, 2017

Applying MVVM to Xamarin.Forms's TabbedPage

For this post I thought we'd dig into an example that came up recently with Caliburn.Micro and the TabbedPage view, specifically how to apply MVVM using Caliburn.Micro.

The TabbedPage view that ships with Xamarin.Forms is a great cross-platform page structure that presents sets of content in different tabs. The layout is surprisingly very similar between iOS, Android and Windows 10. The biggest layout difference is seen in iOS where each tab optionally has an icon.

xamarinforms_tabbedpage

The above image, taken without permission from Xamarin’s documentation, shows iOS, Android and Windows 8.1 Phone. The Windows 10 version is closer to the Android version.

When looking at Xamarin’s documentation, most examples show a series of Pages defined as inline XAML, and applying an ItemTemplate assumes that each tab will have the same layout; there doesn’t seem to be a great way to swap in different pages per tab. Regardless of these short-comings, the most important point regarding these examples is that the TabbedPage view displays the the Title and Icon from the contained Page in the tab headers.

Setting up the ViewModel

The first step in setting up a TabbedPage view is to represent it as its own ViewModel. Caliburn.Micro offers a unique solution to this problem using a pattern it refers to as a Screen Conductor. To understand how this pattern works, Caliburn.Micro treats pages of your application as Screens and the base Screen class contains abstractions for the view lifecycle: OnInitialize, OnActivated, OnDeactivated. These methods, respectively, make it really easy to defer work that shouldn’t be in the constructor, to ensure the view always has the latest data, and to clean-up or prevent navigating away without saving changes. With regards to the TabbedPage, the ScreenConductor provides a simple mechanism to activate and deactivate ViewModels as you navigate between tabs.

Here we define our ScreenConductor as our Main2ViewModel, and Tab1ViewModel and Tab2ViewModel as the contained tabs.

namespace XF.CaliburnMicro1.ViewModels
{
    using Caliburn.Micro;

    public class Main2ViewModel : Conductor<Screen>.Collection.OneActive
    {
        public Main2ViewModel(Tab1ViewModel tab1, Tab2ViewModel tab2)
        {
            Items.Add(tab1);
            Items.Add(tab2);

            ActivateItem(Items[0]);
        }
    }

    public class Tab1ViewModel : Screen
    {
        public Tab1ViewModel()
        {
            DisplayName = "Tab 1";
            Tab1Content = "Tab 1 Content";
        }

        public string Tab1Content { get; set; }
    }

    public class Tab2ViewModel : Screen
    {
        public Tab2ViewModel()
        {
            DisplayName = "Tab 2";
            Tab2Content = "Tab 2 Content";
        }

        public string Tab2Content { get; set; }
    }
}

For completeness sake, these new ViewModels are registered in the App class (defined in my previous post):

public class App : FormsApplication
{
   private readonly SimpleContainer container;

   public App(SimpleContainer container)
   {
       this.container = container;

       // TODO: Register additional viewmodels and services
       container
          .PerRequest<Main2ViewModel>()
          .PerRequest<Tab1ViewModel>()
          .PerRequest<Tab2ViewModel>()
          ;

       Initialize();

       DisplayRootViewFor<Main2ViewModel>();
    }

    // snip...
}

The XAML definitions for these views are represented as a TabbedPage and two instances of ContentPage:

Main2View.xaml
<?xml version="1.0" encoding="UTF-8"?>
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms" 
            xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
            xmlns:converters="clr-namespace:XF.CaliburnMicro1.Converters"
            x:Class="XF.CaliburnMicro1.Views.Main2View"
            ItemsSource="{Binding Items}"
            SelectedItem="{Binding SelectedItem}"
            >

</TabbedPage>
Tab1View.xaml
<?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="XF.CaliburnMicro1.Views.Tab1View"
             Title="{Binding DisplayName}">
  <Label Text="{Binding Tab1Content}" VerticalOptions="Center" HorizontalOptions="Center" />
</ContentPage>
Tab2View.xaml
<?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="XF.CaliburnMicro1.Views.Tab2View"
             Title="{Binding DisplayName}">
  <Label Text="{Binding Tab2Content}" VerticalOptions="Center" HorizontalOptions="Center" />
</ContentPage>

Fixing View / ViewModels for Tabs

If you run the solution as is, you’ll be disappointed. The reason for this is that while Caliburn.Micro can find the View/ViewModel for our MainPage, it doesn’t know how to resolve the View/ViewModels for the tabs. Now the solution I’m going to use leverages a DataTemplateSelector which isn’t something that is traditionally done with Caliburn.Micro. The correct approach with Caliburn.Micro is to take advantage of conventions and special attached properties (eg View.Model). However in this case, the approach I’m using allows you to use your ContentPage inside the TabbedPage or as a standalone page that you can navigate to directly. I’ll cover the other approach in an upcoming post.

We’ll define a DataTemplateSelector that can do the work of finding the View for our ViewModel:

namespace XF.CaliburnMicro1.Converters
{
    using System;
    using System.Collections.Generic;
    using Caliburn.Micro;
    using Caliburn.Micro.Xamarin.Forms;
    using Xamarin.Forms;    

    public class TabbedPageDataTemplateSelector : DataTemplateSelector
    {
        private readonly Dictionary<Type, DataTemplate> _selectors;

        public TabbedPageDataTemplateSelector()
        {
            _selectors = new Dictionary<Type, DataTemplate>();
        }

        protected override DataTemplate OnSelectTemplate(object item, BindableObject container)
        {
            Type viewModelType = item.GetType();

            DataTemplate template = null;

            // check if we've already found the datatemplate for this view
            if (!_selectors.TryGetValue(viewModelType, out template))
            {
                // use caliburn to find the View for this viewmodel
                Type viewType = ViewLocator.LocateTypeForModelType(viewModelType, null, null);

                template = new DataTemplate(() =>
                {
                    var view = Activator.CreateInstance(viewType);

                    var bindableObject = view as BindableObject;

                    if (bindableObject != null)
                    {
                        // when the view's content changes...
                        bindableObject.BindingContextChanged += (sender, args) =>
                        {
                            var page = sender as Page;

                            // leverage a caliburn view lifecyle event
                            // if the viewmodel supports it
                            var viewAware = page?.BindingContext as IViewAware;
                            viewAware?.AttachView(page);
                        };
                    }

                    return view;
                });

                // cache the datatemplate
                _selectors.Add(viewModelType, template);
            }

            // return the correct view for the viewmodel
            return template;
        }
    }
}

Then associate into the view:

<?xml version="1.0" encoding="UTF-8"?>
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms" 
            xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
            xmlns:converters="clr-namespace:XF.CaliburnMicro1.Converters"
            x:Class="XF.CaliburnMicro1.Views.Main2View"
            ItemsSource="{Binding Items}"
            SelectedItem="{Binding SelectedItem}"
            >
    <TabbedPage.ItemTemplate>
        <converters:TabbedPageDataTemplateSelector />
    </TabbedPage.ItemTemplate>
</TabbedPage>

Now when we run our solution, our tabs are correctly populated.

tabbedpage-content

Happy coding!

submit to reddit

Monday, February 20, 2017

Configuring Xamarin.Forms.UWP to use Caliburn Micro

Update 2017/10/15: This walk through is now available as a Visual Studio Template! More details here: https://marketplace.visualstudio.com/items?itemName=BryanBCook.XamarinFormsCaliburnMicroStarterKit

This is the fourth post in this series where we’re configuring a Xamarin.Forms application to use my favourite MVVM framework, Caliburn.Micro. Today’s post will focus on configuring a Universal Windows Application (UWP).

Here’s links to the previous posts:

  1. Getting Started with Xamarin.Forms and Caliburn.Micro
  2. Configuring Xamarin.Forms.Droid to use Caliburn.Micro
  3. Configuring Xamarin.Forms.iOS to use Caliburn.Micro

If you’ve ever built a WPF application, it may seem strange that we’re not going to build XAML pages for our app. Instead, we’re going to leverage the XAML defined in our PCL so that we can reuse as much of the user-interface logic as possible. The pages that we’re adding here act purely as an entry point into our Xamarin.Forms application.

Change App.xaml

Caliburn provides a base application class that we’re going to extend. In our App.xaml, we’ll change the root element to CaliburnApplication, like so:

<cm:CaliburnApplication
    x:Class="XF.CaliburnMicro1.UWP.App"
    xmlns:cm="using:Caliburn.Micro"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    RequestedTheme="Light">
    
</cm:CaliburnApplication>

Configure IoC Container

Next we’ll add the plumbing logic that we’ve added to the other applications to initialize our platform-specific IoC container. Caliburn.Micro has a customized container for UWP, called WinRTContainer, that contains a few helpful registration methods.

I’ve replaced the default App.xaml.cs with this simplified version:

namespace XF.CaliburnMicro1.UWP
{
    using System;
    using System.Collections.Generic;
    using System.Reflection;
    using Windows.ApplicationModel.Activation;
    
    using Caliburn.Micro;
    
    using XF.CaliburnMicro1.ViewModels;

    sealed partial class App
    {
        private WinRTContainer _container;

        public App()
        {
            InitializeComponent();
        }

        protected override void Configure()
        {
            _container = new WinRTContainer();
            _container.RegisterWinRTServices();

            _container.Singleton<XF.CaliburnMicro1.App>();
        }

        protected override void OnLaunched(LaunchActivatedEventArgs args)
        {
            if (args.PreviousExecutionState == ApplicationExecutionState.Running)
                return;

            Xamarin.Forms.Forms.Init(args);

            // loads our MainPage as the root frame
            DisplayRootView<MainPage>();
        }

        #region IoC Overrides
        protected override void BuildUp(object instance)
        {
            _container.BuildUp(instance);
        }

        protected override IEnumerable<object> GetAllInstances(Type service)
        {
            return _container.GetAllInstances(service);
        }

        protected override object GetInstance(Type service, string key)
        {
            return _container.GetInstance(service, key);
        }

        protected override IEnumerable<Assembly> SelectAssemblies()
        {
            return new[]
            {
                GetType().GetTypeInfo().Assembly,
                typeof(MainViewModel).GetTypeInfo().Assembly
            };
        } 
        #endregion
    }
}

Eventually, you’ll want to provide additional logic for initialization and suspension specific to UWP, but for now OnLaunched simply launches MainPage as the root window for our Xamarin.Forms app.

Customize MainPage

The Xamarin.Forms template includes a MainPage that is optimized for Xamarin.Forms, we simply need to load our App from the Singleton defined in Caliburn’s IoC Container.

namespace XF.CaliburnMicro1.UWP
{
    using Caliburn.Micro;

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

            LoadApplication(IoC.Get<XF.CaliburnMicro1.App>());
        }
    }
}

Build and Run

Finally, before we give our UWP a spin, we need to make a minor change the deployment to the Solution.

  1. Change the start-up application to XF.CaliburnMicro.UWP (Universal Windows) and set the target platform to Any CPU
  2. Open the Configuration Manager for the solution

    uwp_configuration
  3. Specify that the solution should Deploy for Debug configuration

    image
  4. Specify Local machine as the target Device
  5. Compile and Run (F5)

You should now see our familiar View / ViewModel from the PCL should be displayed.

Windows 10:

image

Windows 10 Mobile:

image

Next Steps

We’re now set to handle iOS, Android and Universal Windows Apps using Caliburn.Micro, so that’s all for this series. Follow my Xamarin.Forms label to see more posts in this space.

Until then… Happy Coding.

submit to reddit