The implementation is surprisingly simple. But first, don’t forget to set the SimpleStorage context within Android’s MainActivity:
SimpleStorage.SetContext(ApplicationContext);
The PersistentBindableObject derives from BindableObject for two reasons: It allows us to create bindings to visual elements. But most importantly, we’ll make use of its built-in PropertyChanged event. The constructor has an optional key argument, which is used as edit group for SimpleStorage. If the key is null, we skip wiring the properties to SimpleStorage. (This might be handy if we need an instance of this class without persistence.) Finally, there are two important commands: First, we load each property from storage using the property name as key. Second, we subscribe to the PropertyChanged event to write the new value back to storage. That’s all we need for our persistent bindable object.
public abstract class PersistentBindableObject: BindableObject
{
protected PersistentBindableObject(string key = null)
{
if (key == null)
return;
foreach (var property in GetType().GetProperties())
property.SetValue(this, SimpleStorage.EditGroup(key).Get<object>(property.Name));
PropertyChanged += (sender, e) => {
var property = GetType().GetProperty(e.PropertyName);
SimpleStorage.EditGroup(key).Put<object>(e.PropertyName, (object)property.GetValue(this));
};
}
}
Let’s make use of this new class. We’ll implement a new class deriving from PersistentBindableObject.
public class Data: PersistentBindableObject
First, we need to implement the constructor with optional key. We simply call the base implementation with all the persistence magic.
public Data(string key = null) : base(key)
{
}
Now, we define bindable properties of different types. Even if we wouldn’t want to use binding in our project, this mechanism is useful for automatically triggering the PropertyChanged event. Otherwise you’d need to call it manually within the properties’ setters.
public static readonly BindableProperty StringProperty = BindableProperty.Create<Data, string>(p => p.String, "");
public static readonly BindableProperty NumberProperty = BindableProperty.Create<Data, double>(p => p.Number, 0);
public static readonly BindableProperty BooleanProperty = BindableProperty.Create<Data, bool>(p => p.Boolean, false);
Each BindablProperty get’s its corresponding property of appropriate type. The getters and setters need to refer to their bindable property, such that Xamarin.Form’s underlying implementation can react on changed properties and pass their values to bound objects.
public string String {
get{ return (string)GetValue(StringProperty); }
set{ SetValue(StringProperty, (string)value); }
}
public double Number {
get{ return (double)GetValue(NumberProperty); }
set{ SetValue(NumberProperty, (double)value); }
}
public bool Boolean {
get{ return (bool)GetValue(BooleanProperty); }
set{ SetValue(BooleanProperty, (bool)value); }
}
Now, before plugging everything into our MainPage, let’s create a handy extension method to add binding to bindable objects. This method will work for all BindableObjects represented by the generic type T. First, it sets the BindingContext to the source object. Then it sets the actual binding between sourceProperty and targetProperty. Optional arguments of the SetBinding method (mode, converter and stringFormat) are made accessible just like in SetBinding.
public static class BindableObjectExtension
{
public static T BindTo<T>(this T target, BindableObject source,
BindableProperty sourceProperty,
BindableProperty targetProperty,
BindingMode mode = BindingMode.Default,
IValueConverter converter = null,
string stringFormat = null)
where T : BindableObject
{
target.BindingContext = source;
target.SetBinding(targetProperty, sourceProperty.PropertyName, mode, converter, stringFormat);
return target;
}
}
Using our Data class and the handy BindableObjectExtension, we can create our MainPage in just a couple of lines of code. First, we instantiate an object of the Data class using the key "data". This will cause the PersistentBindableObject constructor to load all properties if they exist from a previous run.
var data = new Data("data");
The MainPage consists of six visual elements, all bound to different properties of the data object. The optional arguments like stringFormat allow to fine-tune the binding for individual elements.
MainPage = new ContentPage {
Padding = new Thickness(10, Device.OS == TargetPlatform.iOS ? 30 : 10, 10, 10),
Content = new StackLayout {
Children = {
new Entry().BindTo(data, Data.StringProperty, Entry.TextProperty),
new Label().BindTo(data, Data.StringProperty, Label.TextProperty),
new Slider().BindTo(data, Data.NumberProperty, Slider.ValueProperty),
new Label().BindTo(data, Data.NumberProperty, Label.TextProperty, stringFormat: "{0:0.00}"),
new Switch().BindTo(data, Data.BooleanProperty, Switch.IsToggledProperty),
new Label().BindTo(data, Data.BooleanProperty, Label.TextProperty),
},
},
};
Furthermore, the PersistentBindableObject persists its own derived property BindingContext. If you don’t improve the implementation by excluding it explicitly, you should avoid setting data.BindingContext. Otherwise you possibly send the given context object (maybe a visual element) to storage, which is usually not what you want. But in most cases the persistent bindable object will be the common binding context for many UI elements, so that data doesn’t need a binding context.