Back to School Part 2: Revenge of the Custom Bindings!

Late last summer I wrote a post on creating a custom bindable property using Xamarin.Forms. I titled it “Back to School” because I wrote it around … well … back to school time. Since then however, the Xamarin.Forms team has seen fit to deprecate the function I wrote about that creates the bindable property … and we’re still in the middle of summer!

Ugh – it hasn’t even been a year!

Well, I guess it’s only right of me to update a partially out of date post with the name “back to school” in it … So welcome to Back to School Part 2: Revenge of the Custom Bindings!

I won’t go as in-depth as I did in Part 1, as the details I talk about in that post still stand – however, the API signature to create the binding has changed a bit, and that’s what I want to talk about here.

The API Changes

For the sake of example, I’m going to call the non-deprecated API the “new” API, although it has been around for a while.

New API

public static BindableProperty Create(
    string propertyName, 
    Type returnType, 
    Type declaringType, 
    object defaultValue, 
    BindingMode defaultBindingMode = BindingMode.OneWay, 
    BindableProperty.ValidateValueDelegate validateValue = null, 
    BindableProperty.BindingPropertyChangedDelegate propertyChanged = null, 
    BindableProperty.BindingPropertyChangingDelegate propertyChanging = null, 
    BindableProperty.CoerceValueDelegate coerceValue = null, 
    BindableProperty.CreateDefaultValueDelegate defaultValueCreator = null);

Old API

public static BindableProperty Create<TDeclarer, TPropertyType>(
    Expression<Func<TDeclarer, TPropertyType>> getter, 
    TPropertyType defaultValue, 
    BindingMode defaultBindingMode = BindingMode.OneWay, 
    BindableProperty.ValidateValueDelegate<TPropertyType> validateValue = null, 
    BindableProperty.BindingPropertyChangedDelegate<TPropertyType> propertyChanged = null, 
    BindableProperty.BindingPropertyChangingDelegate<TPropertyType> propertyChanging = null, 
    BindableProperty.CoerceValueDelegate<TPropertyType> coerceValue = null, 
    BindableProperty.CreateDefaultValueDelegate<TDeclarer, TPropertyType> defaultValueCreator = null) where TDeclarer : BindableObject;

Buy Why?!?

At first glance the old API looks a bit messier, but once you dig in you find that in fact it really is more elegant. The generics in the old signature provide a means to strongly type everything that follows (plus the added bonus of IDE provided intellisense). Not to mention there are less parameters – we don’t have to explicitly define the return and declaring type.

So if the old API was so great – then why did it change? That’s exactly what I asked Twitter. And Twitter did what Twitter does … Adam Patridge (fellow Xamarin MVP) came back with the answer:

If you click on through to Jason Smith’s & Adam’s conversation, it turns out that there’s a ton of bloat introduced with the old BindableProperty.Create() API by iOS and its AOT compiling (interested in Xamarin.iOS at that level? Here you go.)

OK – so the API is deprecated for our own (and our app’s) good! So let’s now look at using the other / new one.

Creating the Bindable Property

The API signature is pretty straight forward, with the first 4 parameters being required (and I always specify the default binding mode, the fifth parameter, as well – if only to be explicit).

For this example, imagine that we’re extending a normal Label control, and adding a boolean property to it that we want to be able to bind to our view model. The class will be called NewLabel and the property will be called IsNew . (Original, I know.)

The bare minimum we have to do to create the binding is the following:

public bool IsNew
{
    get { return (bool)GetValue(IsNewProperty); }
    set { SetValue(IsNewProperty, value); }
}
 
public static readonly BindableProperty IsNewProperty = BindableProperty.Create(
    nameof(IsNew),
    typeof(bool),
    typeof(NewLabel),
    false,
    BindingMode.TwoWay
);

After that in the BindableProperty.Create function are all of the delegates. Their purpose stays exactly the same. The only thing that changes is that the parameters representing the value of the binding (in our case the boolean) are no longer strongly typed.

Let’s take a quick look at them:

  • public delegate bool ValidateValueDelegate (BindableObject bindable, object value);
  • public delegate void BindingPropertyChangedDelegate (BindableObject bindable, object oldValue, object newValue);
  • public delegate void BindingPropertyChangingDelegate (BindableObject bindable, object oldValue, object newValue);
  • public delegate object CoerceValueDelegate (BindableObject bindable, object value)
  • public delegate object CreateDefaultValueDelegate (BindableObject bindable);

So when implementing these delegates, BindableObject will always be the thing that’s getting bound to … or our NewLabel instance. Then we’ll have to cast any “value” parameters to the type of value we’re dealing with. For a full explanation of what each of these delegates does and the order they are invoked – see the original post.

Summary

That’s it! The API signature that provided the ability to create a bindable property, including delegates to handle the verification and changing of the value of the property in a strongly typed manner is gone. It’s gone for a good reason – it led to bloating app sizes.

We’re still able to create the custom binding – but not in a strongly typed manner any longer. So we’re trading off having to do a lot of casting in exchange for smaller app sizes.

I’ll take it.