Damian Mehers' Blog Android, VR and Wearables from Geneva, Switzerland.

27Sep/160

Using Styles and Data Triggers to disable Xamarin forms while waiting

It's a common scenario: You are sending data to a service, or waiting for something to happen, and you don't want the user to interact with your form while that is happening.

untitled-1

The naive approach is to bind the IsEnabled property on your containing Layout to a boolean property in your View Model, but you'll soon find that IsEnabled is not inherited. Setting it on a StackLayout doesn't set it on all the controls embedded within that layout.

Here is a solution which binds the IsRunning property of an ActivityIndicator to a View Model property, and then uses a Style and a DataTrigger to react to the ActivityIndicator's running by setting the IsEnabled properties on the Layouts contained controls:

First I overlay an ActivityIndicator over my form using an AbsoluteLayout:

  <AbsoluteLayout>
    <ActivityIndicator
        IsRunning="{Binding Loading}" HorizontalOptions="Center" VerticalOptions="Center"
        IsVisible="{Binding Loading}" AbsoluteLayout.LayoutBounds="0,0,1,1"
        AbsoluteLayout.LayoutFlags="All" x:Name="ActivityIndicator" />
    <StackLayout Orientation="Vertical"
                 AbsoluteLayout.LayoutBounds="0,0,1,1" AbsoluteLayout.LayoutFlags="All">

Next in my StackLayout I define an explicit Style with a DataTrigger which disables the targeted control and sets its Opacity to 30% when the ActivityIndicator is running:

      <StackLayout.Resources>
        <ResourceDictionary>
          <Style TargetType="View" x:Key="MyBase">
            <!-- Disable controls when the activity indicator is running -->
            <Style.Triggers>
              <DataTrigger
                  TargetType="View"
                  Binding="{Binding Source={x:Reference ActivityIndicator}, Path=IsRunning}"
                  Value="True">
                <Setter Property="Opacity" Value="0.3" />
                <Setter Property="IsEnabled" Value="False" />
              </DataTrigger>
            </Style.Triggers>
          </Style>

You might hope that using an implicit style instead of an explicit style above would affect all views contained within the StackLayout, but it doesn't work like that. There is also the tantalizing and undocumented ApplyToDerivedTypes Style property, but that has no impact that I am aware of.

So instead I create implicit styles for each specific type of control I use inside my StackPanel:

          <!-- Define implicit styles for each control we use. -->
          <Style TargetType="Label" BasedOn="{StaticResource MyBase}" />
          <Style TargetType="Entry" BasedOn="{StaticResource MyBase}" />
          <Style TargetType="Button" BasedOn="{StaticResource MyBase}" />
        </ResourceDictionary>
      </StackLayout.Resources>

At least I'm able to reuse my base style. So here is my final complete view (but I'm not quite done yet):

<?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:shared="clr-namespace:LoadingDemo.Shared;assembly=LoadingDemo.Shared"
             BindingContext="{x:Static shared:Locator.MyViewModel}"
             x:Class="LoadingDemo.MainPage">

  <!-- Use an absolute layout to overlay one control over another -->
  <AbsoluteLayout>
    <ActivityIndicator
        IsRunning="{Binding Loading}" HorizontalOptions="Center" VerticalOptions="Center"
        IsVisible="{Binding Loading}" AbsoluteLayout.LayoutBounds="0,0,1,1"
        AbsoluteLayout.LayoutFlags="All" x:Name="ActivityIndicator" />

    <StackLayout Orientation="Vertical"
                 AbsoluteLayout.LayoutBounds="0,0,1,1" AbsoluteLayout.LayoutFlags="All">

      <StackLayout.Resources>
        <ResourceDictionary>
          <Style TargetType="View" x:Key="MyBase">
            <!-- Disable controls when the activity indicator is running -->
            <Style.Triggers>
              <DataTrigger
                  TargetType="View"
                  Binding="{Binding Source={x:Reference ActivityIndicator}, Path=IsRunning}"
                  Value="True">
                <Setter Property="Opacity" Value="0.3" />
                <Setter Property="IsEnabled" Value="False" />
              </DataTrigger>
            </Style.Triggers>
          </Style>

          <!-- Define implicit styles for each control we use. -->
          <Style TargetType="Label" BasedOn="{StaticResource MyBase}" />
          <Style TargetType="Entry" BasedOn="{StaticResource MyBase}" />
          <Style TargetType="Button" BasedOn="{StaticResource MyBase}" />
        </ResourceDictionary>
      </StackLayout.Resources>

      <Label Text="My Label" HorizontalOptions="Center" />
      <Entry Placeholder="Enter text here" />
      <Entry Placeholder="Enter text here" />
      <Entry Placeholder="Enter text here" />
      <Button Text="Click Me" Command="{Binding StartCommand}" HorizontalOptions="Center" />
    </StackLayout>
  </AbsoluteLayout>
</ContentPage>

This is my View Model:

  public class ViewModel : INotifyPropertyChanged {
    private bool _loading;

    public ViewModel() {
      StartCommand = new Command(Start);
    }

    private async void Start() {
      Loading = true;
      await Task.Delay(TimeSpan.FromSeconds(5));
      Loading = false;
    }

    public bool Loading {
      get {
        return _loading;
      }
      private set {
        _loading = value;
        OnPropertyChanged();
      }
    }

    public ICommand StartCommand { get; }

    public event PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyChanged([CallerMemberName] string propertyName = null) {
      PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
  }

When I test the above code in an app I created, everything works swimmingly except for the Button. It doesn't become disabled when I click it.

The reason is that I am using the Command property to execute code in my View Model, and the ICommand interface to which it is bound has its very own CanExecute mechanism to decide when the Button can be clicked.

The solution is in the View Model, and involves raising the CanExecuteChanged event:

    public ViewModel() {
      // When you click the button run the Start method.  The command is available
      // when not loading
      StartCommand = new Command(Start, canExecute: () => !Loading);
    }
...
public bool Loading {
      get { ... }
      private set {
        _loading = value;
        OnPropertyChanged();
        StartCommand.ChangeCanExecute();
      }
    }

Here is the final View Model:

  public class ViewModel : INotifyPropertyChanged {
    private bool _loading;

    public ViewModel() {
      // When you click the button run the Start method.  The command is available
      // when not loading
      StartCommand = new Command(Start, () => !Loading);
    }

    private async void Start() {
      Loading = true;
      await Task.Delay(TimeSpan.FromSeconds(5));
      Loading = false;
    }

    public bool Loading {
      get {
        Debug.WriteLine($"Returning {_loading}");
        return _loading;
      }
      private set {
        _loading = value;
        OnPropertyChanged();
        StartCommand.ChangeCanExecute();
      }
    }

    public Command StartCommand { get; }

    public event PropertyChangedEventHandler PropertyChanged;
    private void OnPropertyChanged([CallerMemberName] string propertyName = null) {
      PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
  }

I like to put my View Models in a separate class library, which has no dependencies on Xamarin Forms, but the Command class comes from Xamarin Forms. Fortunately Xamarin Forms is now open source, so I can "borrow" the Command class' source and embed it within my class library and thus remove the Xamarin Forms dependency.

The complete solution is here in GitHub.

4Sep/162

UWP OAuth in Xamarin Forms using Xamarin.Auth

tl;dr: Complete standalone example here.

I recently wanted to authenticate to Evernote via OAuth in a Xamarin Forms app I'm creating.

There is an excellent Xamarin plugin, called Xamarin.Auth which lets you do the OAuth dance for iOS and Android Xamarin Forms apps, but even in the latest branch, I couldn't get it working on the Universal Windows Platform (UWP) app.

Comments pointed to using a the WebAuthenticationBroker from Microsoft. There are plenty of examples here, unfortunately none for Evernote.

I created and published an Evernote UWP OAuth example using WebAuthenticationBroker based on the Twitter example, which was similar, but not similar enough to be able to just copy/paste.

Once I had this working I was back to Xamarin Forms,and put together a complete standalone example using Google, to log you in to Google and then display your email address and photo. Here it is running in UWP:

uwp 04

I published that example on GitHub.

In order to make Xamarin.Auth work, you create a platform specific page renderer which does the OAuth. I'd already done this for iOS and Android. For Windows I implemented it like this:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading.Tasks;
using Windows.Security.Authentication.Web;
using Windows.Web.Http;
using Newtonsoft.Json;
using Xamarin.Auth;
using Xamarin.Forms.Platform.UWP;
using XamFormsUWPOAuth;
using XamFormsUWPOAuth.Shared;
using XamFormsUWPOAuth.UWP;

[assembly: ExportRenderer(typeof(AuthenticationPage), typeof(AuthenticationPageRenderer))]

namespace XamFormsUWPOAuth.UWP {
  class AuthenticationPageRenderer : PageRenderer {
    private bool _isShown;

    protected override async void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) {
      base.OnElementPropertyChanged(sender, e);
      if (_isShown) return;
      _isShown = true;

      var code = await AuthenticateUsingWebAuthenticationBroker();
      var account = await ConvertCodeToAccount(code);
      await AuthenticationHelper.FetchGoogleEmailAndPicture(account);
    }


    private async Task<string> AuthenticateUsingWebAuthenticationBroker() {
      var googleUrl = Constants.AuthorizeUrl + "?client_id=" +
                      Uri.EscapeDataString(Constants.GoogleClientId);
      googleUrl += "&redirect_uri=" + Uri.EscapeDataString(Constants.GoogleCallbackUrl);
      googleUrl += "&response_type=code";
      googleUrl += "&scope=" + Uri.EscapeDataString(Constants.Scope);

      var startUri = new Uri(googleUrl);

      var webAuthenticationResult =
        await
          WebAuthenticationBroker.AuthenticateAsync(WebAuthenticationOptions.None, startUri,
            new Uri(Constants.GoogleCallbackUrl));
      return webAuthenticationResult.ResponseStatus != WebAuthenticationStatus.Success ? null : webAuthenticationResult.ResponseData.Substring(webAuthenticationResult.ResponseData.IndexOf('=') + 1);
    }


    private static async Task<Account> ConvertCodeToAccount(string code) {
      var httpClient = new HttpClient();
      IHttpContent content = new HttpFormUrlEncodedContent(new Dictionary<string, string> {
        {"code", code},
        {"client_id", Constants.GoogleClientId},
        {"client_secret", Constants.GoogleClientSecret},
        {"redirect_uri", Constants.GoogleCallbackUrl},
        {"grant_type", "authorization_code"},
      });
      var accessTokenResponse = await httpClient.PostAsync(new Uri(Constants.AccessTokenUrl), content);
      var responseDict =
        JsonConvert.DeserializeObject<Dictionary<string, string>>(accessTokenResponse.Content.ToString());

      return new Account(null, responseDict);
    }
  }
}

You could use one of the other Microsoft OAuth examples, or your own, in order to do the OAuth.

The AccountStore stuff is a little different. I wanted to reuse the AccountStore goodness that comes with Xamarin.Auth, but I needed a UWP AccountStore implementation. I also needed to ensure my shared code, in my shared project, picked up my UWP AccountStore. I did this by creating a simple container class in the shared project:

using System;
using Xamarin.Auth;

namespace XamFormsUWPOAuth.Shared {
public static class AccountStoreFactory {
    public static Func<AccountStore> Create { get; set; } = () => AccountStore.Create();
  }
}

I use this in order to get at the shared AccountStore throughout my code, rather than using AccountStore.Create() which you'd normally do. In my UWP startup code, I overwrite the default AccountStore (which doesn't exist on UWP anyway) in my App.xaml.cs :

    protected override void OnLaunched(LaunchActivatedEventArgs e) {
      AccountStoreFactory.Create = () => new UWPAccountStore();
      Frame rootFrame = Window.Current.Content as Frame;
              ...

My UWP specific AccountStore implementation was based on this one in the portable-bait-and-switch branch)

It makes use of the Igor Kulman's DataProtectionExtension implementation here.

uwp 01

uwp 03

uwp 04

Check out the full standalone example that works with iOS, Android and of course UWP in my GitHub repository.

Filed under: Xamarin 2 Comments