Dangers of referencing elements by name

December 11th 2020 Xamarin

When following the MVVM pattern and using a view model for a Xamarin.Forms page, most of the bindings will simply bind to the view model properties. However, there are still cases when that's not enough and a reference by name to another element is required.

Here's an example of such a case:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:NameReference"
             x:Class="NameReference.MainPage"
             x:Name="root">

  <FlexLayout BindableLayout.ItemsSource="{Binding Users}">
    <BindableLayout.ItemTemplate>
      <DataTemplate>
          <local:Avatar Text="{Binding Initials}">
            <local:Avatar.GestureRecognizers>
              <TapGestureRecognizer
                Command="{Binding BindingContext.UserTappedCommand,
                                  Source={x:Reference root}}"
                CommandParameter="{Binding}"/>
            </local:Avatar.GestureRecognizers>
          </local:Avatar>
      </DataTemplate>
    </BindableLayout.ItemTemplate>
  </FlexLayout>

</ContentPage>

The TapGestureRecognizer Command binding references the page element by name to get access to its view model where the command is defined. This is necessary because the parent element of the gesture is in a DataTemplate for items in the bound (Users) collection and its binding context is set to a specific in that collection.

In most cases, this works just fine. But can you think of something that would make it not work (assuming all the reference properties are defined as expected)?

Well, the Avatar template could be a reason for that. For example, the following one:

<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="NameReference.Avatar"
             x:Name="root">
  <ContentView.Content>
    <Grid>
      <Grid.RowDefinitions>
        <RowDefinition Height="64"/>
      </Grid.RowDefinitions>
      <Grid.ColumnDefinitions>
        <ColumnDefinition Width="64"/>
      </Grid.ColumnDefinitions>
      <BoxView BackgroundColor="Bisque"
               CornerRadius="32"/>
      <Label Text="{Binding Text, Source={x:Reference root}}"
             FontSize="24"
             FontAttributes="Bold"
             HorizontalOptions="Center"
             VerticalOptions="Center"/>
    </Grid>
  </ContentView.Content>
</ContentView>

Why? Because it declares the same name: root. Although the name is in a separate template belonging to a different view, it still interferes with the name reference in the parent page using that view. You can avoid this problem by using unique names; not just within a template but across all of them. One way to ensure that is to use prefixes for names, e.g. avatar_root instead of root for the template of the Avatar view.

You can check out the described behavior yourself in the sample project from my GitHub repository. The latest commit contains working code. The one before that features the broken command binding because of the duplicate name.

When using names in Xamarin.Forms XAML templates make sure that they are always globally unique to avoid broken bindings that can be difficult to troubleshoot and fix. Using prefixes for names in each template is a simple and reliable way to achieve that.

Get notified when a new blog post is published (usually every Friday):

If you're looking for online one-on-one mentorship on a related topic, you can find me on Codementor.
If you need a team of experienced software engineers to help you with a project, contact us at Razum.
Copyright
Creative Commons License