Xamarin.Forms - Working with Visual States (Part I - Binding)

There's multiple ways of handling visual states of your app pages, views or controls. Depending on how you utilize these techniques, they all might be fine to use. Moreover, you don't need to stick with just one way of handling your states, they all have there pros and cons.

In this part of this blog post series I will focus on the direct binding of view model boolean flags to the UI element visibility property and go through their positive and negative aspects.

Binding

Binding a boolean flag is probably most commonly used way of dealing with visual states in apps taking advantage of the MVVM architecture. This is driven by the fact that we all work with bindings all the time and it comes naturally to add yet another one to show or hide an element. I can say with almost 100% certainty that at some point of writing XAML code you've bound a bool flag in your view model to the  IsVisible property of an UI element to toggle its visibility. Don't worry, that's perfectly fine, even though there are designated tools to maintain visual state in your application (i.e. Visual State Manager). However, you should be aware that this method of controlling visual states is quite dangerous if abused.

As a general rule of thumb, I use binding to boolean flags to control visual states only in situations when there's two states that the page, control or a view can be in. For instance, if you have a ListView (or should I say CollectionView - unfortunately still in preview at the time of writing this blog post) with elements that can be marked by the user as favourite and there's no other state for that element to be in, then I wouldn't mind introducing IsFavourite flag in the view model and then binding it to the IsVisible property on a UI element indicating marked as favourite state. This scenario could be illustrated with the following code:

public class Movie : NotifyPropertyChanged
{
    private bool isFavourite;
    private string imageUri;

    public bool IsFavourite
    {
        get => isFavourite;
        set => SetProperty(ref isFavourite, value);
    }
    
    public string ImageUri
    {
        get => imageUri;
        set => SetProperty(ref imageUri, value);
    }
}
<ContentPage.Resources>
    <ResourceDictionary>
      <DataTemplate x:Key="MovieItemTemplate"
                    x:DataType="models:Movie">
        <ViewCell>
            <Grid Padding="10">            
                <Image Source="{Binding ImageUri, Mode=Oneway}" />
                <Image Source="favIcon.png"
                       VerticalOptions="End"
                       HorizontalOptions="End"
                       IsVisible="{Binding IsFavourite, Mode=OneWay}" />
            </Grid>
        </ViewCell>
      </DataTemplate>
    </ResourceDictionary>
</ContentPage.Resources>

<ListView Grid.Row="1"
          ItemsSource="{Binding Movies, Mode=OneWay}"
          ItemTemplate="{StaticResource MovieItemTemplate}">
</ListView>

As briefly mentioned in the first paragraph, this technique is not always safe to use. What I mean by that is that it can quickly go out of control and turn into spaghetti code.

Consider a simple example of having a list of movies to show on a page. The number of boolean flags indicating which state the page is currently in could be easily 5 or more:

  1. Empty - no data returned by the APIs (e.g. when certain filters applied)
  2. Loading - showing loading indicator
  3. Loaded - showing list of movies
  4. Loading Error - APIs failed to return with positive results (e.g. no Internet connection)
  5. Try Again - after API failure the user should have the ability to try retrieving the data again

The above list could grow to much larger number if we add new functionalities to our movies page. For instance, ability to search (by title or description) would add another few or more states. To complicate things even more let's consider allowing user to filter movies by genre or rating. That would give us probably more than 10+ boolean flags to control and that's not even considering any other list unrelated functionality that could potentially be part of this page. The point is, that it's not a good idea to manage so many flags, as it creates a lot of boilerplate code, it's very error prone when refactoring and it tends to be hard to read and comprehend.

Below is a code sample of a view model, which could be handling the basic movies page visual states.

public class MoviesViewModel : BaseViewModel
{
    private IMoviesService moviesService;

    private bool isEmpty;
    private bool isLoading;
    private bool isLoaded;
    private bool isLoadingError;
    private bool isTryAgain;

    public MoviesViewModel(IMoviesService moviesService)
    {
        this.moviesService = moviesService;
    }

    public bool IsEmpty
    {
        get => isEmpty;
        set => SetProperty(ref isEmpty, value);
    }

    public bool IsLoading
    {
        get => isLoading;
        set => SetProperty(ref isLoading, value);
    }

    public bool IsLoaded
    {
        get => isLoaded;
        set => SetProperty(ref isLoaded, value);
    }

    public bool IsLoadingError
    {
        get => isLoadingError;
        set => SetProperty(ref isLoadingError, value);
    }

    public bool IsTryAgain
    {
        get => isTryAgain;
        set => SetProperty(ref isTryAgain, value);
    }

    public async Task LoadData()
    {
        // Clear all the other flags to be sure we're not in any other state
        IsEmpty = false;
        IsLoadingError = false;
        IsLoaded = false;

        IsLoading = true;

        await RetrieveMovies();

        IsLoading = false;
    }

    public async Task TryAgain()
    {
        // Clear all the other flags to be sure we're not in any other state
        IsEmpty = false;
        IsLoadingError = false;
        IsLoaded = false;

        IsTryAgain = true;

        await RetrieveMovies();

        IsTryAgain = false;
    }

    private async Task RetrieveMovies()
    {
        try
        {
            var movies = await moviesService.RetrieveMovies();
            if (!(movies?.Any() ?? false))
            {
                IsEmpty = true;
            }
            else
            {
                IsLoaded = true;
            }
        }
        catch (Exception ex)
        {
            IsLoadingError = true;
        }
    }
}

The main problems with the above are that it requires from a developer to generate a lot of code, that it makes refactoring or maintaining the code to be error prone (by not setting/resetting flags appropriately) and that the code can very easily get tangled because of convoluted conditions dependant on certain state (aka flags) being active or not.

Fortunately, there is a pretty easy way of improving the above code. It's as easy as introducing an enum that would represent visual states.

public enum MoviesPageVisualStates
{
    Base, // default state (aka no state yet)
    Empty,
    Loading,
    Loaded,
    LoadingError,
    TryAgain
}

Straight away you can see how much less writing you would need to do in your view model and how much clearer that code is.

public class MoviesViewModel : BaseViewModel
{
    private IMoviesService moviesService;

    private MoviesPageVisualStates moviesPageVisualState;

    public MoviesViewModel(IMoviesService moviesService)
    {
        this.moviesService = moviesService;
    }

    public MoviesPageVisualStates MoviesPageVisualState
    {
        get => moviesPageVisualState;
        set => SetProperty(ref moviesPageVisualState, value);
    }

    public async Task LoadData()
    {
        MoviesPageVisualState = MoviesPageVisualStates.Loading;

        await RetrieveMovies();
    }

    public async Task TryAgain()
    {
        MoviesPageVisualState = MoviesPageVisualStates.TryAgain;

        await RetrieveMovies();
    }

    private async Task RetrieveMovies()
    {
        try
        {
            var movies = await moviesService.RetrieveMovies();
            if (!(movies?.Any() ?? false))
            {
                MoviesPageVisualState = MoviesPageVisualStates.Empty;
            }
            else
            {
                MoviesPageVisualState = MoviesPageVisualStates.Loaded;
            }
        }
        catch (Exception ex)
        {
            MoviesPageVisualState = MoviesPageVisualStates.LoadingError;
        }
    }
}

Obviously, this change would require some adjustments in the UI layer because we no longer work with bool flags, which could be bound directly to IsVisible property, but with MoviesPageVisualStates. Introduction of a converter is probably the first thing that comes to mind to fix the problem, but instead of doing that we should take advantage of DataTriggers.

I will go into details on how DataTriggers can help you with controlling visual states in your application in the next part of this series of blog posts.

Summary

It is fine to use boolean flags to toggle visibility of the UI elements in your application. However, it shouldn't be considered a primary way of handling visual states and should be used only in very simple cases.