Using XAML in Xamarin.Forms – Part 3: Accessing Dynamic Data

The first article in this series on using XAML in Xamarin.Forms looked at what XAML is, its benefits, and some of the basics of using it. The second article dove a little deeper, looking at how to access static data – or data that doesn't change – from XAML.

I could stop right there if all Xamarin.Forms apps did was display data once and the screens never changed, but alas, that's not the case. The app needs to support dynamic data, or data that changes at runtime. That is what this article will cover: how to incorporate dynamic data with XAML in a Xamarin.Forms app using dynamic resources and data binding.

Dynamic Resources

In the previous article, I mentioned one way to store and subsequently retrieve static data was via the ResourceDictionary property on a class that inherits from VisualElement – and generally whatever is read from the ResourceDictionary does not change.

The code to access a static value in a ResourceDictionary looks like the following:

<ContentPage.Resources>
    <ResourceDictionary>
        <x:String x:Key="RecipeNameLabel">Recipe Name</x:String>
    </ResourceDictionary>
</ContentPage.Resources>

<!-- Some other code here -->

<EntryCell Label="{StaticResource RecipeNameLabel}" /> 

However, should the need ever arise to change that EntryCell's label – it can't be done…or can it?

This is where DynamicResources come to the rescue!

A DynamicResource allows the value of the ResourceDictionary object to be changed at runtime, and that updated value will be reflected in whatever property that references the object.

Setting up a ResourceDictionary to use a DynamicResource is exactly the same as for StaticResources – no changes are needed. The change comes in on how the object is referenced – and, as you may have guessed by now, it's referenced by the DynamicResource keyword.

So in the example from above, the EntryCell control would access the RecipeNameLabel object in the ResourceDictionary as follows:

<EntryCell Label="{DynamicResource RecipeNameLabel}" />

The difference between DynamicResource and StaticResource is that the XAML compiler will only access anything defined as a StaticResource once, pop in the value and then leave it be. Whereas with DynamicResource a link is maintained between that dictionary item and the property it's set to.

The natural question then is – how does the RecipeNameLabel entry in the ResourceDictionary get changed?

That will have to happen in the XAML's code-behind file.

In response to some external event, the Resources dictionary is accessed, and the dictionary item is updated – exactly in the same way that any dictionary item is updated.

In the example below, a button that indicates the recipe is "the world's greatest" is clicked.

worldsGreatest.Clicked += (sender, e) => {
    Resources["RecipeNameLabel"] = "The world's greatest recipe name:";
};

The ResourceDictionary is located in the Resources property.

DynamicResources are not the only way to update data – Xamarin.Forms comes with a powerful data binding engine that allows properties (and events) of controls to be bound to (special) properties and actions of various classes.

Data Binding

Updating a control's property value through a DynamicResource is all well and fine – but it's not robust enough to make use of on a large scale. One of the benefits that I've been touting about XAML is that all of the code for the user interface can be expressed in the XAML markup and a lot of logic can stay out of the code-behind file.

An excellent way to keep true to that benefit is to use data binding.

Data binding allows a control to be bound to a backing model (or something that purely represents data) class and have both various properties of the control and the model be updated whenever either changes… without having to handle any events.

In other words – imagine there is a class that models recipe data. Naturally it would have properties for the recipe's name, ingredients, directions, and so on. That class would be used to both capture data from the user interface and then save the recipe to a data store of some type.

In the case of the recipe data entry, data binding saves the developer from writing a bunch of boilerplate code that sets the Recipe's properties to the various properties of the controls that display them – and then more code to move the new values from the controls back into the Recipe object. This becomes especially tedious when that same model class is used throughout the app. Thus databinding frees the developer to focus on implementing core app logic instead of boilerplate code…like getting the Recipe to save to the data store.

Setting up the XAML code to make use of data binding is simple – all one needs to do is set whatever property you want to bind equal to the Binding keyword and then the model's property that should be bound to.

A simple form that displays and updates recipes would look like the following:

<TableView Intent="Form">
    <TableView.Root>
        <TableSection Title="Enter Data">
            <EntryCell Label="Recipe Name" Text="{Binding RecipeName}" />
            <EntryCell Label="Ingredients" Text="{Binding Ingredients}" />
            <EntryCell Label="Directions" Text="{Binding Directions}" />
        </TableSection>
    </TableView.Root>
</TableView>

But…there is some extra work that needs to be done in the model class that enables the two way communication between it and the view. Specifically, it must implement the INotifyPropertyChanged interface.

It's this interface that allows the control's on the UI to know that something has changed, and vice versa.

Only one event must be implemented as part of INotifyPropertyChanged:

public event PropertyChangedEventHandler PropertyChanged;

By invoking that event every time a property changes value, the user interface will automatically get updated. And by extension, the model will get updated every time the user interface changes.

The Recipe class, when fully implemented will look like the following:

public class Recipe : INotifyPropertyChanged
{
    string _recipeName;
    public string RecipeName
    {
        get => _recipeName;
        set
        {
            if (string.Equals(_recipeName, value))
                return;

            _recipeName = value;

            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(RecipeName)));
        }
    }

    string _ingredients;
    public string Ingredients
    {
        get => _ingredients;
        set
        {
            if (string.Equals(_ingredients, value))
                return;

            _ingredients = value;

            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Ingredients)));
        }
    }

    string _directions;
    public string Directions
    {
        get => _directions;
        set
        {
            if (string.Equals(_directions, value))
                return;

            _directions = value;

            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Directions)));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

A couple things to note, the first is that if the passed in value of the property is the same as what is already there – nothing happens. You don't want to have the binding fire to set something that is already set.

The second is that I'm making sure something is subscribed to the PropertyChanged event before invoking it – to avoid any null exceptions.

Then there is a final piece of the puzzle to get data binding to work – associating the model to the Page that hosts the controls – and that's done through the BindingContext property.

You can set the BindingContext either in the code-behind file, or in XAML by using the constructor syntax you learned about in the last article to create the model. Here's an example of setting the BindingContext on the ContentPage using the XAML constructor syntax.

<ContentPage.BindingContext>
    <local:Recipe />
</ContentPage.BindingContext>

Here's an example of a button on a Xamarin.Forms page simulating a database update. Pretend the button click event is actually a database service call coming back and updating the model – you can see how the control's in the UI get updated instantly.

dbSimulation.Clicked += (s, e) =>
{
    // The recipe object would be returned from the data store - this is only for demo
    var bc = BindingContext as Recipe;

    bc.RecipeName = "new recipe";
    bc.Ingredients = "new ingredients";
    bc.Directions = "new directions";
};

It should be noted that anything in Xamarin.Forms that inherits from BindableObject has a BindingContext property. Controls are one thing that inherit from BindableObject. So it is possible to have different controls bound to different data sources all on the same page. The BindingContext of a parent control will be applied to all of its child controls – so setting it at the page level (as done above at the ContentPage) – will be sure to make all the controls beneath it have the same BindingContext.

One thing you may be saying to yourself is, "I don't want to mess my models up with INotifyPropertyChanged"…and I don't blame you. There is a design pattern around that, and it's called MVVM, or Model-View-ViewModel, where the ViewModel implements INotifyPropertyChanged. I'll cover that in a future article.

Summary

In this article you learned that controls defined in XAML don't have to have static properties that are set at design time and never change. Or they only change by modifying the XAML's code-behind file. You learned that XAML controls can update their values dynamically when data attached to them changes.

The first method was through DynamicResources, which uses items defined in a ResourceDictionary, and updates those at runtime.

The second is databinding. The databinding engine is powerful, it allows both the UI to be updated whenever the model class it is bound to is updated, and the UI to update the model.

Comments