Creating Mobile Apps with Xamarin.Forms: Infrastructure

  • 10/1/2014

Version 5. Data binding

Xamarin.Forms is mostly about user interfaces, but user interfaces rarely exist in isolation. A case in point is the application being built in this chapter. This user interface is really a visual representation of data and the means through which that data is manipulated.

To accommodate such scenarios, .NET and Xamarin.Forms provide some built-in facilities for smoothing the links between data and user interfaces. The NoteTaker5 program incorporates some of these features. In particular, you’ll see here how to automate the process of data binding: linking two properties of two objects so that changes to one property automatically trigger a change to the other.

Streamlining INotifyPropertyChanged classes

Classes that implement INotifyPropertyChanged usually have rather more changeable properties than the Note class. For this reason, it’s a good idea to simplify the set accessors, if only to avoid mixing up property and field names, or misspelling the text property name.

One simplification is to encapsulate the actual firing of the event in a protected virtual method:

protected virtual void OnPropertyChanged(string propertyName)
{
    if (PropertyChanged != null)
    {
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

Now the Title property looks like this:

public string Title
{
    set
    {
        if (title != value)
        {
            title = value;
            OnPropertyChanged("Title");
        }
    }
    get
    {
        return title;
    }
}

The OnPropertyChanged method is made protected and virtual because you might write an enhanced Note class that derives from this Note class but includes more properties. The derived class needs access to this method.

But let’s pause a moment to improve out INotifyPropertyChanged handling. It’s recommended to obtain the handler first, and then perform the check for null on and the event call on that same object:

protected virtual void OnPropertyChanged(string propertyName)
{
    PropertyChangedEventHandler handler = PropertyChanged;
    if (handler != null)
        handler(this, new PropertyChangedEventArgs(propertyName));
}

In a multithreaded environment, a PropertyChanged handler might be detached between the null check and the call, and this code prevents a null-reference exception from occurring.

You can go further in streamlining the OnPropertyChanged method. C# 5.0 introduced support for CallerMemberNameAttribute and some related attributes. This attribute allows you to replace an optional method argument with the name of the calling method or property.

In the OnPropertyChanged method, make the argument optional by assigning null to it and precede it with CallerMemberName in square brackets:

protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
    PropertyChangedEventHandler handler = PropertyChanged;
    if (handler != null)
        handler(this, new PropertyChangedEventArgs(propertyName));
}

You’ll need a using directive for System.Runtime.CompilerServices for that attribute. Now the Title property can call OnPropertyChanged with no arguments, and the propertyName argument will automatically be set to the property name “Title” because that’s where the call to OnPropertyChanged is originating:

public string Title
{
    set
    {
        if (title != value)
        {
            title = value;
            OnPropertyChanged();
        }
    }
    get
    {
        return title;
    }
}

This avoids a potentially misspelled text property name, and allows property names to be changed during program development without worrying about also changing text strings. One of the primary reasons the CallerMemberName was invented was to simplify classes that implement INotifyPropertyChanged.

It’s possible to go even further. You’ll need to define a generic method named SetProperty (for example) with the CallerMemberName attribute, but you’ll need to remove it from OnProperty-Changed:

bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
    if (Object.Equals(storage, value))
        return false;

    storage = value;
    OnPropertyChanged(propertyName);
    return true;
}

protected virtual void OnPropertyChanged(string propertyName)
{
    PropertyChangedEventHandler handler = PropertyChanged;
    if (handler != null)
        handler(this, new PropertyChangedEventArgs(propertyName));
}

The SetProperty method requires access to the backing field and the new value, but automates the rest of the process and returns true if the property was changed. (You might need to use this return value if you’re doing some additional processing in the set accessor.) Now the Title property looks like this:

public string Title
{
    set
    {
        SetProperty(ref title, value);
    }
    get
    {
        return title;
    }
}

Although SetProperty is a generic method, the C# compiler can deduce the type from the arguments. The whole property definition has become so short, you can write the accessors concisely on single lines without obscuring the operations:

public string Title
{
    set { SetProperty(ref title, value); }
    get { return title; }
}

Here is the new Note class in the PCL project NoteTaker5:

class Note : INotifyPropertyChanged
{
    string title, text;

    public event PropertyChangedEventHandler PropertyChanged;

    public string Title
    {
        set { SetProperty(ref title, value); }
        get { return title; }
    }

    public string Text
    {
        set { SetProperty(ref text, value); }
        get { return text; }
    }

    public void Save(string filename)
    {
        string text = this.Title + "\n" + this.Text;
        FileHelper.WriteAllText(filename, text, () => { });
    }

    public void Load(string filename)
    {
        FileHelper.ReadAllText(filename, (string text) =>
            {
                // Break string into Title and Text.
                int index = text.IndexOf('\n');
                this.Title = text.Substring(0, index);
                this.Text = text.Substring(index + 1);
            });
    }

    bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
    {
        if (Object.Equals(storage, value))
            return false;

        storage = value;
        OnPropertyChanged(propertyName);
        return true;
    }

    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
            handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

This type of streamlining obviously makes much more sense for classes with more than just two properties, but then it begins making lots of sense.

A peek into BindableObject and bindable properties

You’ve seen how you can define a class that implements the INotifyPropertyChanged interface. You’ll probably be interested to learn that many of the classes in Xamarin.Forms—including all the view, layout, and page classes—also implement INotifyPropertyChanged. All these classes have a PropertyChanged event that your applications can use to be notified when properties of these classes change.

For example, the NoteTaker4 program keeps the Title property of the Note class updated from the Text property of the Entry view like so:

entry.TextChanged += (sender, args) =>
    {
        note.Title = args.NewTextValue;
    };

You can do pretty much the same thing by installing a handler for the PropertyChanged event of the Entry view and checking for the Text property:

entry.PropertyChanged += (sender, args) =>
    {
        if (args.PropertyName == "Text")
            note.Title = entry.Text;
    };

In a sense, the TextChanged event defined by Entry and Editor is redundant and unnecessary. It is provided solely for purposes of programmer convenience.

Many of the classes in Xamarin.Forms implement INotifyPropertyChanged automatically because they derive from a class named BindableObject that implements this interface. BindableObject also defines a protected virtual method named OnPropertyChanged.

As its name implies, BindableObject is important to the support of data binding in Xamarin.-Forms, yet the implementation of INotifyPropertyChanged by this class is only part of the story and, to be honest, the easier part.

Let’s begin exploring the more arcane part of BindableObject with some experimentation. In one of the previous versions of the NoteTaker program, try initializing the Entry view with some text:

entry.Text = "This is some text";

Now when you run the program the Entry is initialized with this text. But now try replacing that property setting with this method call:

entry.SetValue(Entry.TextProperty, "This is some text");

This works as well. These two statements are functionally identical.

Look at that weird first argument to SetValue: It’s something called Entry.TextProperty, which indicates that it’s static, but it’s not a property at all. It’s static field of the Entry class. It’s also read-only, and it’s defined in the Entry class something like this:

public static readonly BindableProperty TextProperty;

It’s a little odd for a field of a class to be named TextProperty, but there it is. Because it’s static, however, it exists independently of any Entry objects that might or might not exist.

If you look in the documentation of the Entry class, you’ll see that it defines four properties—Text, TextColor, IsPassword, and Placeholder—and you’ll also see four corresponding public static read-only fields of type BindableProperty with the names TextProperty, TextColor-Property, IsPasswordProperty, and PlaceholderProperty.

These properties and fields are closely related. Indeed, internal to the Entry class, the Text property is defined like this:

public string Text
{
    set { SetValue(Entry.TextProperty, value); }
    get { return (string)GetValue(Entry.TextProperty); }
}

So you see why it is that your application calling SetValue on Entry.TextProperty is exactly equivalent to setting the Text property and perhaps just a tinier bit faster!

The internal definition of the Text property in Entry isn’t secret information. This is standard code. The SetValue and GetValue methods are defined by BindableObject, the same class that implements INotifyPropertyChanged for many Xamarin.Forms classes. All the real work involved with maintaining the Text property is going on in these SetValue and GetValue calls. Casting is required for the GetValue method because it’s defined as returning object.

The static Entry.TextProperty object is of type BindableProperty, which you might correctly surmise is a class related to BindableObject, but it’s important to keep them distinct in your mind: BindableObject is the class from which many Xamarin.Forms classes derive and that provides support for objects of type BindableProperty.

The BindableProperty objects effectively extend the functionality of standard C# properties. Bindable properties provide systematic ways to:

  • Define properties
  • Give properties default values
  • Store their current values
  • Provide mechanisms for validating property values
  • Maintain consistency among related properties in a single class
  • Respond to property changes
  • Trigger notifications when a property is about to change and has changed

In addition, BindableObject and BindableProperty provide mechanisms for animation and data binding. They are a vital part of the infrastructure of Xamarin.Florms.

The close relationship of a property named Text with a BindableProperty named Text-Property is reflected in the way that programmers speak about these properties: Sometimes a programmer says that the Text property is “backed by” a BindableProperty named TextProperty because TextProperty provides infrastructure support for the Text property. But a common shortcut is to say that Text is itself a “bindable property” and generally no one will be confused.

Not every Xamarin.Forms property is a bindable property. Neither the Content property of ContentPage nor the Children property of Layout<T> (from which StackLayout derives) is a bindable property. Sometimes changes to nonbindable properties result in the PropertyChanged event being fired, but that’s not guaranteed. The PropertyChanged event is only guaranteed for bindable properties.

Later in this book you’ll see how to define your own bindable properties.

Automated data bindings

As you’ve seen, it’s possible to install event handlers on the Entry and Editor to determine when the Text property changes, and to use that occasion to set the Title and Text properties of the Note class. Similarly, you can use the PropertyChanged event of the Note class to keep the Entry and Editor updated.

In other words, the NoteTaker4 program has paired up objects and properties so that they track each other’s values. As one property changes, the other is updated.

It turns out that tasks like this are very common, and this is why Xamarin.Forms allows such tasks to be automated with a technique called data binding.

Data bindings involve a source and a target. The source is the object and property that changes, and the target is the object and property that is changed as a result. But that’s a simplification. Although the distinction between target and source is clearly defined in any particular data binding, sometimes the properties affect each other in different ways: Sometimes the target causes the source to be updated, and sometimes the source and target update each other.

The data binding mechanism in Xamarin.Forms uses the PropertyChanged event to determine when a source property has changed. Therefore, a source property used in a data binding must be part of a class that implements INotifyPropertyChanged (either directly or through inheritance), and the class must fire a PropertyChanged event when that property changes. (Actually this is not entirely true: If the source property never changes, a PropertyChanged event is not required for the data binding to work, but it’s only a “one time” data binding and not very interesting.)

The target property of a data binding must be backed by a BindableProperty. As you’ll see, this requirement is imposed by the programming interface for data bindings. You cannot set a data binding without referencing a BindableProperty object!

In the NoteTaker5 program, the Text properties of Entry and Editor are the binding targets because they are backed by bindable properties named TextProperty. The Title and Text properties of the Note class are the binding sources. The Note class implements INotifyProperty-0Changed so PropertyChanged events are fired when these source properties changes.

You can define a data binding to keep a target property updated with the value of a source property with two statements: The first statement associates the two objects by setting the BindingContext property of the target object to the source object. In this case that’s the instance of the Note class:

entry.BindingContext = note;

The second statement makes use of a SetBinding method on the target. These SetBinding calls come in several different forms. BindableObject itself defines one SetBinding method, and the BindableObjectExtensions class defines two SetBinding extension methods. Here’s the simplest:

entry.SetBinding(Entry.TextProperty, "Title");

You’ll see more complex data bindings in the chapters ahead. The target property here is the Text property of Entry. That’s specified in the first argument. The Title property—specified here as a text string—is assumed to be a property of whichever object has been defined as the BindingContext of the Entry object, which in this case is a Note object.

Similarly, you can set a data binding for the Editor:

editor.BindingContext = note;
editor.SetBinding(Editor.TextProperty, "Text");

The first argument of SetBinding must be a BindableProperty object defined by the target class (Editor in this case) or inherited by the target class.

Part of the infrastructure that BindableProperty provides is a default binding mode that defines the relationship between the source and the target. The BindingMode enumeration has four members:

  • Default
  • OneWay — source updates target, the normal case
  • OneWayToSource — source updates target
  • TwoWay — source and target update each other

For the TextProperty objects defined by Entry and Editor, the default binding mode is BindingMode.TwoWay. This means that these four statements actually define a two-way data binding: Any change to the Title property of the Note object is reflected in the Text property of the Entry, and vice versa, and the same goes for the Editor.

The NoteTaker4 program included three event handlers—the PropertyChanged handler for the Note class and the TextChanged handlers for the Entry and Editor—to keep these pairs of properties in synchronization. Those three event handlers are no longer required if they are replaced by the four statements you’ve just seen:

entry.BindingContext = note;
entry.SetBinding(Entry.TextProperty, "Title");

editor.BindingContext = note;
editor.SetBinding(Editor.TextProperty, "Text");

Internally, these four statements result in similar event handlers being set. That’s how the data-binding mechanism works.

However, these four statements can actually be reduced to three statements. Here’s how:

The BindingContext property has a very special characteristic. It is very likely that more than one data binding on a page has the same BindingContext. That is true for this little example. For this reason, the BindingContext property is propagated through the visual tree of a page. In other words, if you set the BindingContext on a page, it will propagate to all the views on that page except for those views that have their own BindingContext properties set to something else. You can set BindingContext on a StackLayout and it will propagate to all the children (and other descendants) of that StackLayout. The two BindingContext settings shown above can be replaced with one set on the page itself:

this.BindingContext = note;

These automated data bindings are part of the NoteTaker5Page class. Also, the handlers for the Load and Save buttons have become lambda functions, all the variables have been moved to the constructor, and the code is looking quite sleek at this point:

class NoteTaker5Page : ContentPage
{
    static readonly string FILENAME = "test.note";

    public NoteTaker5Page()
    {
        // Create Entry and Editor views.
        Entry entry = new Entry
        {
            Placeholder = "Title (optional)"
        };

        Editor editor = new Editor
        {
            Keyboard = Keyboard.Create(KeyboardFlags.All),
            BackgroundColor = Device.OnPlatform(Color.Default,
                                                Color.Default,
                                                Color.White),
            VerticalOptions = LayoutOptions.FillAndExpand
        };

        // Set data bindings.
        Note note = new Note();
        this.BindingContext = note;
        entry.SetBinding(Entry.TextProperty, "Title");
        editor.SetBinding(Editor.TextProperty, "Text");

        // Create Save and Load buttons.
        Button saveButton = new Button
        {
            Text = "Save",
            HorizontalOptions = LayoutOptions.CenterAndExpand
        };

        Button loadButton = new Button
        {
            Text = "Load",
            IsEnabled = false,
            HorizontalOptions = LayoutOptions.CenterAndExpand
        };

        // Set Clicked handlers.
        saveButton.Clicked += (sender, args) =>
            {
                note.Save(FILENAME);
                loadButton.IsEnabled = true;
            };

        loadButton.Clicked += (sender, args) => note.Load(FILENAME);

        // Check if the file is available.
        FileHelper.Exists(FILENAME, (exists) =>
            {
                loadButton.IsEnabled = exists;
            });

        // Assemble page.
        this.Padding = new Thickness(10, Device.OnPlatform(20, 0, 0), 10, 0);

        this.Content = new StackLayout
        {
            Children =
            {
                new Label
                {
                    Text = "Title:"
                },
                entry,
                new Label
                {
                    Text = "Note:"
                },
                editor,
                new StackLayout
                {
                    Orientation = StackOrientation.Horizontal,
                    Children =
                    {
                        saveButton,
                        loadButton
                    }
                }
            }
        };
    }
}

By replacing the explicit event handlers with data bindings, we’ve also managed to forget all about the problem of the PropertyChanged event in the Note class being fired from a secondary thread. When you use a data binding, you don’t have to worry about that.

Eventually, the final NoteTaker application won’t be dealing with a single Note object. It will have a whole collection of Note objects. With these data bindings in place, you can view and edit any one of these Note objects by setting it to the BindingContext of the page. But that’s for the next chapter.

Meanwhile, let’s see if we can simplify this page code even more. One prime candidate is the code that enables the Load button based on the FileHelper.Exists call. Perhaps solving that problem will simplify some other parts of the program as well.