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