Visual State Management with BuildIt.States and Uno

I’ve posted previously on using visual states in Uno and how they can be used to effectively manage the different visual layouts a page can take on. These may be changes in layout due to the application being resized, or perhaps due to different data loading states. I’ve recently created a Uno build of the BuildIt.States library, BuildIt.States.Uno. In this post I’m going to walk through using this library to help manage visual states from within your view model.

One of the topics that’s quite hot at the moment is whether XAML has had its day and whether the new coded UI techniques provide a better solution. I’m not going to go too far into this debate but one of the things I really like about XAML is the separation of the UI (declared in XAML) from its data representation (the ViewModel).

Whilst the data binding framework of both UWP and Xamarin.Forms works well for connecting properties on a ViewModel to attributes of a UI element, there is no way to connect visual states on the page, with some aspect of the ViewModel. This is where the BuildIt.States library kicks in.

Thinking about Visual States

Before we get into using the BuildIt.States library, lets start by thinking through the different states that our page can go through. For the purpose of this post we’re going to build a simple app with the following spec:

  • The app has a single page that has a button, “Load Data”
  • When the button is pressed, the button will be hidden and a loading indicator will be shown.
  • The app will attempt to load some data.
  • Loading data will randomly succeed or fail
  • When loading is complete (either succeed or fail) the loading indicator will be hidden, a message will be displayed indicating success or failure, and the Load Data button will be displayed again.
  • If the Load Data button is pressed again, the status of the previous attempt will remain on screen until the new attempt to load data has been completed.

An initial read of this spec would seem to indicate that there is a single set of states:

  • Not Loading
  • Loading
  • Loaded – Success
  • Loaded – Failed

However, when the button is pressed for the second time, there are two more states that need to be included : Loading (Loaded – Success) and Loading (Loaded – Failed).

An alternative way of thinking about the states is that there is a group of states that pertain to whether data is being loaded, and a different group of states that pertain to whether the page has data:

LoadingStates

  • NotLoading
  • Loading

These states control whether the button or the loading indicator is visible

DataStates

  • NoData
  • Data
  • DataFailedToLoad

These states control the visibility of the data, or in this case the message indicating success or failure of the data loading.

Visual States in XAML

Since the focus of this post isn’t on how to design a page in XAML I’ll skip over the steps involved in laying out the page. In summary, after creating a new Uno project (using the Uno project templates), I opened the MainPage.xaml in Visual Studio Blend. Whilst Blend is a long way from being the design-first tool it was once envisaged as, it does still have support for defining visual states, which is sorely missing in Visual Studio (and probably the only reason I still use Blend).

Using the design surface in Blend I’m able to layout the various TextBlock, Button and ProgressRing to build the simple UI for the app. I then use the States tool window to create two Visual State Groups (LoadingStates and DataStates) and the associated Visual States for showing the appropriate elements. The resulting XAML looks like the following:

<Page x:Class="BuildItWithStatesForUno.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:local="using:BuildItWithStatesForUno"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="LoadingStates">
                <VisualState x:Name="NotLoading" />
                <VisualState x:Name="Loading">
                    <VisualState.Setters>
                        <Setter Target="LoadDataButton.(UIElement.Visibility)" Value="Collapsed" />
                        <Setter Target="LoadingProgress.(UIElement.Visibility)" Value="Visible" />
                        <Setter Target="LoadingProgress.(ProgressRing.IsActive)" Value="True" />
                    </VisualState.Setters>
                </VisualState>
            </VisualStateGroup>
            <VisualStateGroup x:Name="DataStates">
                <VisualState x:Name="NoData" />
                <VisualState x:Name="Data">
                    <VisualState.Setters>
                        <Setter Target="DataSuccessTextBlock.(UIElement.Visibility)" Value="Visible" />
                    </VisualState.Setters>
                </VisualState>
                <VisualState x:Name="DataFailedToLoad">
                    <VisualState.Setters>
                        <Setter Target="DataFailedTextBlock.(UIElement.Visibility)" Value="Visible" />
                    </VisualState.Setters>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
        <StackPanel HorizontalAlignment="Center"
                    VerticalAlignment="Center">
            <TextBlock x:Name="DataSuccessTextBlock"
                       Margin="20"
                       FontSize="40"
                       Foreground="Green"
                       Text="Data Loaded Successfully!"
                       TextAlignment="Center"
                       Visibility="Collapsed" />
            <TextBlock x:Name="DataFailedTextBlock"
                       Margin="20"
                       FontSize="40"
                       Foreground="Red"
                       Text="Data Failed to Load"
                       TextAlignment="Center"
                       Visibility="Collapsed" />
            <Grid Margin="50">
                <Button x:Name="LoadDataButton"
                        HorizontalAlignment="Center"
                        Click="LoadDataClick"
                        Content="Load Data"
                        FontSize="30" />
                <ProgressRing x:Name="LoadingProgress"
                              Width="50"
                              Height="50"
                              Foreground="Blue"
                              IsActive="False"
                              Visibility="Collapsed" />
            </Grid>
        </StackPanel>
    </Grid>
</Page>

Testing the Visual States

Before moving on I wanted to test that the Visual States work, so I wired up the Load Data button with an event handler so I could switch the visual states:

private async void LoadDataClick(object sender, RoutedEventArgs e)
{
    var loadTimeInMilliseconds = rnd.Next(1000, 10000);
    var success = loadTimeInMilliseconds % 2 > 0;
    VisualStateManager.GoToState(this, "Loading", true);
    await Task.Delay(loadTimeInMilliseconds);
    VisualStateManager.GoToState(this, "NotLoading", true);
    VisualStateManager.GoToState(this, success ? "Data" : "DataFailedToLoad", true);
}

Clearly this code isn’t production ready (string literals, codebehind, no error handling etc) but when I run the app, I can see the desired state changes. Here I’m just showing Android but since it’s Uno, it should work nicely on iOS, UWP and WASM too.

Visual States in Action (Gif captured and generated using Snagit from TechSmith)

ViewModel States

Now that we’ve defined the visual states, we need a way to both control and track the states in our ViewModel. In the same way that we can use data binding to update attributes of the visual elements on the page, we need a way to mirror visual states within our ViewModel. This is where we can make use of the StateManger from the BuildIt.States library.

In the following MainViewModel, a StateManager is created and setup using two different state groups. Rather than using string literals, we use an enum to define each state group. Note that each enum has a Base value, which reflects the default, or unset, state.

public enum LoadingStates
{
    Base,
    NotLoading,
    Loading
}

public enum DataStates
{
    Base,
    Data,
    DataFailedToLoad
}

public class MainViewModel : IHasStates
{
    private readonly Random rnd = new Random();

    public IStateManager StateManager { get; } = new StateManager();

    public MainViewModel()
    {
        StateManager
            .Group<LoadingStates>()
            .DefineAllStates()
            .Group<DataStates>()
            .DefineAllStates();
    }

    public async Task LoadData()
    {
        var loadTimeInMilliseconds = rnd.Next(1000, 10000);
        var success = loadTimeInMilliseconds % 2 > 0;

        await StateManager.GoToState(LoadingStates.Loading);
        await Task.Delay(loadTimeInMilliseconds);
        await StateManager.GoToState(LoadingStates.NotLoading);
        await StateManager.GoToState(LoadingStates.Loading);
        await StateManager.GoToState(success ? DataStates.Data : DataStates.DataFailedToLoad);
    }
}

I also need to update the codebehind in MainPage to create an instance of MainViewModel and then invoke the LoadData method when the Load Data button is clicked:

public sealed partial class MainPage : Page
{
    public MainViewModel ViewModel => DataContext as MainViewModel;

    public MainPage()
    {
        this.InitializeComponent();

        DataContext = new MainViewModel();
    }
    private async void LoadDataClick(object sender, RoutedEventArgs e)
    {
        await ViewModel.LoadData();
    }
}

Connecting ViewModel to Visual States

Of course, if we run the code at this point, the state tracked within the MainViewModel don’t update the UI. To complete the loop, we need to attach another StateManager to the visual states defined on MainPage, and then bind the two StateManagers so that they can remain in sync. This is all done in the MainPage codebehind.

public sealed partial class MainPage : Page
{
    public MainViewModel ViewModel => DataContext as MainViewModel;
    private IStateManager StateManager { get; } = new StateManager();

    public MainPage()
    {
        this.InitializeComponent();

        DataContext = new MainViewModel();

        StateManager
            .Group<LoadingStates>()
            .DefineAllStates(this,this.LoadingStates)
            .Group<DataStates>()
            .DefineAllStates(this, this.DataStates);

        StateManager.Bind(ViewModel.StateManager);
    }
    private async void LoadDataClick(object sender, RoutedEventArgs e)
    {
        await ViewModel.LoadData();
    }
}

And there we go – now when you run the application and press the Load Data button the LoadData method on the MainViewModel will be invoked. As the states of the MainViewModel change, the StateManager and subsequently the visual states on the MainPage are updated.

Design Time Data for Xamarin.Forms

Design Time Data for Xamarin.Forms

In my previous post I showed how to switch between Visual States using the tooling that comes with the BuildIt.Forms library. One of the other features of the tooling is the ability to load mock data that can assist with visualising how a page might look like with certain data. Rather than try to guess at what data your page might require, the tooling simply allows you to define a series of design actions. Each design action will appear within the BuildIt.Forms flyout, allowing you to invoke the action.

Let’s demonstrate this with an example. I’m going to change the layout of my page slightly so that in the DataLoaded state a ListView is displayed that takes up the entire screen. The XAML for the ListView is as follows:

<ListView x_Name=”DataList” IsVisible=”false”>
     <ListView.ItemTemplate>
         <DataTemplate>
             <ViewCell>
                 <Label Text=”{Binding Name}” />
             </ViewCell>
         </DataTemplate>
     </ListView.ItemTemplate>
</ListView>

As I don’t have any actual data at the moment, when I run up the application and click the Load Data button I see the following for the DataLoaded state:

image

This isn’t great as I’ve got no idea what my ListView is going to look like. So let’s fix this by adding a design action. I do this by calling the AddDesignAction method (it’s an extension method which is why I can access it on the MainPage) and providing a name, “Mock Data”, and the action to perform when the design action is run.

public MainPage()
{
     InitializeComponent();


    var groups = VisualStateManager.GetVisualStateGroups(this);

#if DEBUG
     this.AddDesignAction(“Mock Data”,
         () =>
         {
             var data = from i in new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }
                        select new { Name = $”Item {i}” };
             DataList.ItemsSource = data;
         });
#endif
}

In this case I’m creating an IEnumerable of an anonymous type that has a property Name, which aligns with the data binding in the ListView XAML shown earlier. I’m assigning this directly to the ItemsSource of the ListView – at this stage I’m just creating the layout of the pages of my application so I might not even have View Models, which is why I’m assigning directly to the ItemSource property in place of data binding it.

Now when I run the application I see:

imageimage

imageimage

The final image shows the list of items being displayed in the ListView – clearly this layout could do with some work!

Visual State Manager Tooling in Xamarin.Forms With BuildIt.States

Visual State Manager Tooling in Xamarin.Forms With BuildIt.States

Back in the days of Silverlight/Windows Phone Microsoft launched a tool called Expression Blend that allowed developers and designer to work in harmony with developers doing their thing (ie write code) in Visual Studio and designers creating the user experience in XAML using Expression Blend. Fast forward a few years and Expression Blend has been rebadged to Blend for Visual Studio and most of the features of Blend have now been migrated to Visual Studio. With the demise of Windows Phone and the lack of developer interest in building for just Windows, Blend is now a tool that most developers have all but forgotten. So, why am I bringing this up now? Well, one of the features I missed from Blend is the ability to have design time data that allows you to build out the entire user interface, with the design time data being replace by real data when the application is run. Whilst there have been some attempts at providing a design time experience for Xamain/Xamarin.Forms, the reality is that it comes no where close to what Blend was able to do in its heyday.

If we look at other platforms, such as React Native, there has been a shift away from design time experience, across to an interactive runtime experience. By this I mean the ability to adjust layout and logic of the application whilst it’s running, which relies on the platform being able to hot-reload both layout and logic of the application. There are some third party tools for Xamarin.Forms that partially enable this functionality.

One of the challenges I found when working with Visual States is that you often need to get the application to a certain point, or follow a particular sequence of steps in order to get a specific Visual State to appear. Take the example I provided in my previous post on page states where I provided DataLoaded and DataFailedToLoad states – in the example the appearance of these states was completely random, so you might have to click the button a couple of times in order to get the state to appear. Luckily, the BuildIt.Forms library has a slightly-hidden feature that allows you to manually switch between states. I say it’s slightly-hidden because if you connect your Visual States to a StateManager in your ViewModels (shown in either this post or this post) you’ll see this feature automatically. In the example I covered in my previous post I needed to add the following line to the end of the MainPage constructor:

public MainPage()
{
     InitializeComponent();
    var groups = VisualStateManager.GetVisualStateGroups(this);
}

Now, when I run the application I see a small dot appear in the bottom left of the screen.

image

Clicking on the dot reveals a flyout that allows you to switch states:

App14UWP20190317125937

Note that this is only shown when the Visual Studio debugger is attached so will not impact the way your application works in release mode.

Page State with the Visual States Manager for Xamarin.Forms

Page State with the Visual States Manager for Xamarin.Forms

One of the current limitations of the Xamarin.Forms implementation of the Visual State Manager (VSM) is that it only works for setting properties on an individual control. Whilst this is great for control state management (think button states like disabled, pressed etc), its incredibly limiting and makes it unsuitable for some typical visual state scenarios. The one that often comes up in a mobile application is for pages that load data. In this scenario you typically have a least three states: Loading, DataLoaded, DataFailedToLoad. In some cases you might even extend this to have states such as Refreshing or LoadingMoreData. For these states you probably want to show/hide different elements on the screen, which is why the current Xamarin.Forms implementation of the VSM isn’t a great option.

Luckily there’s an alternative, which is the Visual State Manager that’s part of the BuildIt.Forms library. Here’s a quick example of it in action:

<?xml version=”1.0″ encoding=”utf-8″ ?>
<ContentPage
             
             
              x_Class=”App14.MainPage”>
     <vsm:VisualStateManager.VisualStateGroups>
         <vsm:VisualStateGroups>
             <vsm:VisualStateGroup Name=”LoadingStates”>
                 <vsm:VisualState Name=”Loading”>
                     <vsm:VisualState.Setters>
                         <vsm:Setter Value=”True” Element=”{x:Reference LoadingLabel}” Property=”IsVisible” />
                     </vsm:VisualState.Setters>
                 </vsm:VisualState>
                 <vsm:VisualState Name=”DataLoaded”>
                     <vsm:VisualState.Setters>
                         <vsm:Setter Value=”True” Element=”{x:Reference LoadedLabel}” Property=”IsVisible” />
                     </vsm:VisualState.Setters>
                 </vsm:VisualState>
                 <vsm:VisualState Name=”DataFailedToLoad”>
                     <vsm:VisualState.Setters>
                         <vsm:Setter Value=”True” Element=”{x:Reference FailedLabel}” Property=”IsVisible” />
                     </vsm:VisualState.Setters>
                 </vsm:VisualState>
             </vsm:VisualStateGroup>
         </vsm:VisualStateGroups>
     </vsm:VisualStateManager.VisualStateGroups>

     <StackLayout VerticalOptions=”Center”>
         <Label Text=”Loading…” x_Name=”LoadingLabel” HorizontalOptions=”Center”  IsVisible=”False” />
         <Label Text=”Success: Data Loaded!!” x_Name=”LoadedLabel” HorizontalOptions=”Center” IsVisible=”False” />
         <Label Text=”Failure :-(” x_Name=”FailedLabel” HorizontalOptions=”Center” IsVisible=”False” />
         <Button Text=”Load Data” Clicked=”LoadClicked” />
     </StackLayout>
</ContentPage>

In this case we’ve defined three Visual States that correspond to showing the LoadingLabel, LoadedLabel and FailedLabel respectively. The code behind for the LoadClicked method illustrates how easily you can switch between the states:

private readonly Random rnd = new Random();


private async void LoadClicked(object sender, EventArgs e)
{
     var success = rnd.Next(0, 1000) % 2 == 0;
     await VisualStateManager.GoToState(this, “Loading”);
     await Task.Delay(2000);
     await VisualStateManager.GoToState(this, success ? “DataLoaded” : “DataFailedToLoad”);
}

Ok, so one last thing we can add in is a bit of animation to make the transition between states a little smoother. Let’s fade our labels in and out:

<vsm:VisualState Name=”Loading”>
     <vsm:VisualState.ArrivingAnimations>
         <animations:AnimationGroup>
             <animations:AnimationGroup.PostAnimations>
                 <animations:FadeAnimation Opacity=”1″
                                           Duration=”500″
                                           Element=”{x:Reference LoadingLabel}” />
             </animations:AnimationGroup.PostAnimations>
         </animations:AnimationGroup>
     </vsm:VisualState.ArrivingAnimations>
     <vsm:VisualState.LeavingAnimations>
         <animations:AnimationGroup>
             <animations:AnimationGroup.PreAnimations>
                 <animations:FadeAnimation Opacity=”0″
                                           Duration=”500″
                                           Element=”{x:Reference LoadingLabel}” />
             </animations:AnimationGroup.PreAnimations>
         </animations:AnimationGroup>
     </vsm:VisualState.LeavingAnimations>

     <vsm:VisualState.Setters>
         <vsm:Setter Value=”True” Element=”{x:Reference LoadingLabel}” Property=”IsVisible” />
     </vsm:VisualState.Setters>
</vsm:VisualState>

The XAML adds a fade in after the state transition has occurred (Post animation) when transitioning to (Arriving at) the the Loading state, and a fade out before the state transition has occurred (Pre animation) when transitioning from (Leaving) the Loading state. As you can see the XAML gets fairly verbose but it’s structured this way to allow for complex combinations and sequences of animations:

<animations:AnimationGroup.PostAnimations>
     <animations:SequenceAnimation>
         <animations:ParallelAnimation>
             <animations:FadeAnimation Opacity=”1″ Duration=”500″ Element=”{x:Reference LoadingLabel}” />
             <animations:ScaleAnimation Scale=”5″ Duration=”500″ Element=”{x:Reference LoadingLabel}” />
             <animations:SequenceAnimation>
                 <animations:RotateAnimation Rotation=”10″ Duration=”250″ Target=”LoadingLabel” />
                 <animations:RotateAnimation Rotation=”0″ Duration=”250″ Target=”LoadingLabel” />
                 <animations:RotateAnimation Rotation=”-10″ Duration=”250″ Target=”LoadingLabel” />
                 <animations:RotateAnimation Rotation=”0″ Duration=”250″ Target=”LoadingLabel” />
             </animations:SequenceAnimation>
         </animations:ParallelAnimation>
         <animations:ScaleAnimation Scale=”1″ Duration=”500″ Element=”{x:Reference LoadingLabel}” />
     </animations:SequenceAnimation>
</animations:AnimationGroup.PostAnimations>

And let’s see this in action:

App14UWP20190317022241

Hopefully you can see from this short post how you can leverage the BuildIt.Forms Visual State Manager to do complex page state management as well as animations. We’ve just released a new beta package compatible with the latest Xamarin.Forms v3.6 and would love feedback (https://www.nuget.org/packages/BuildIt.Forms/2.0.0.27-beta).