The Plan

Before making a plan about observable bindings, let's think back about a normal classical binding:

<Entry Text="{Binding Name}"/>

This creates a Binder object that synchronizes the BindableProperty with the Property from the ViewModel. The binding object knows when either of them updates by registering to the PropertyChanged event. Both BindableObject and the ViewModel implement INotifyPropertyChanged.

Normally a MarkupExtension simply returns a value when ProvideValue() is called. But the BindingExtension uses it to hook up the previously mentioned event handlers. We are going to do the same thing here. Except, we're going to subscribe to an observable.

We create a class called BindExtension. By following the name convention for MarkupExtensions, that means that in XAML we can simply write o:Bind where o is a prefix for the namespace.
We continue by creating a Path and a Mode property. We can simply re-use the BindingMode Type from the original BindingExtension. Notice how we set Path as the ContentProperty. This way, you don't have to write Path in the expression.

<Entry Text="{o:Bind Path=Name}"/>
<Entry Text="{o:Bind Name}"/> <!-- "Name" is used as the Path-->

If we want to listen to an observable and update the BindableProperty, we need to get a reference to both. Let's start with retrieving the BindableProperty and it's BindableObject using the IProvideValueTarget Service.

The BindingContext might change over time that's why we need to register for the BindingContextChanged event. There, we will remove any previous setup and call SetupBinding() for the new BindingContext. More about SetupBinding() in the next part.

Once we have the BindableObject and its BindingContext, we need to resolve the path of the expression. We can use this little helper method:

If the BindingMode is not set, it looks up the default BindingMode for this BindableProperty

If the BindingMode indicates we need to update the View from the ViewModel, we assume that the bound object is an observable and set up a listening mechanism to update the view.

If the BindingMode indicates we need to update the ViewModel from the View, we assume that the bound object is an observer and set up an emitter to send new values to the observer.

ViewModel → View

In this part we will create a mechanism to update our View based upon an observable. If we think back about classical Binding, the ViewModel usually implements INotifyPropertyChanged. So there, the BindingExtension simply subscribes to the PropertyChanged event. And when that event fires, it updates the view accoridingly. What we need to do is replace this event with a subscription to our observable.

This code brings us a long way. You can see that whenever a new value is emitted by the observable, the BindableObject will be updated by using SetValue().

Whether ObserveOn(SynchronizationContext.Current) should be called here, is up for debate. By adding this, no matter which thread sends the new value, the BindableObject will always be updated on the UI Thread. But maybe that's the responsibility of the ViewModel and not this Binding. The classical Binding does not do this, but it is a simple small addition here.

But, there is a problem.

We assumed that our observable can be cast to type IObservable<object>. Which seems to be ok. SetValue() receives an object anyway, so we don't care about the exact type. Plus IObservable<in T> is covariant, which means: If A inherits B then IObservable<A> inherits IObservable<B>. That's great since everything inherits object this should work.

But no, it doesn't.

Covariance and contravariance in generic type parameters are supported for reference types, but they are not supported for value types. (source)

So that means this mechanism doesn't work for e.g. double and int.

In order to fix this, we will need to exact type of the observable. SubscribePropertyForObservable becomes:

As you can see, we need reflection to get the exact Type. First we get T from the implemented interface IObservable<T> and then we need to call SubscribePropertyForObservable<T> with that type. That also means we have to construct and call SubscribePropertyForObservable<T> at runtime.

Once again we are forced to use reflection. But it does not impact the performance of the subscription, only the initial set up.

The listenSubscription is kept in a global field, so it can be cleaned up when the BindingContext changes. When a Visual is removed from the Visual Tree, its BindingContext is set to null. So relying on the BindingContextChanged event should suffice, and cause no memory leaks.

View → ViewModel

In this last part we will send changes in our view to an observer. In classical Binding, when a BindableProperty is modified, it simply sets the bound property on the BindingContext. Here, this Property will be an observer, and we'll have to call OnNext() when a new value arrives.

It will take two steps to reach our goal:

Create an observable for the BindableProperty

Subscribe the observer to that observable

First of all, how do you know that a BindableProperty received a new value? In Xamarin.Forms, BindableObject simply implements INotifyPropertyChanged, you can subscribe to the PropertyChanged event:

This is an extension method that can be applied to any BindableObject. FromEventPattern can turn any event into an observable. After specifying how to set and remove the handler, we added a filter for the property name. Remember that the PropertyChanged event will fire for any property. But we are only interested in one. Finally, we don't want to return the EventArgs of PropertyChanged, we should return the value for that property. We can use the GetValue() method for that.

Subscribing to the observable is quite easy. However, we do need the exact type again for the same reason as before.

Once more, emitSubscription is kept in a global field, so it can be cleaned up when the BindingContext changes.

Finally, we need to get the type of IObsever<T>. This is very easy since we can just use bindingProperty.ReturnType. Calling the generic method means constructing it at runtime using reflection and then invoking it. Just like we did in the previous part.