Wednesday, March 30, 2016

Xamarin.Forms validation using Behaviors

I''m starting with Xamarin not long time ago. Facing with validation problem, I found very useful class Behaviors. It's not all about validation but also can do more cool things with this class. In this post, I will focus on my way to use it in validation for client side.

The first thing, maybe you should look at this post:
https://blog.xamarin.com/behaviors-in-xamarin-forms/

Base on this, I do some changing code.
 - In the real thing, you may like to validate many condition for one controls (such as: Require and email format, require and maxlength...). To make it easier I create validation class to validate for each control type, instead of each rule. Let's see the code is changed:


   1:  
   3:  using Xamarin.Forms;
   4:   
   5:  namespace MyProject.Client.Framework.Validators
   6:  {
   7:      public class EntryValidatorBehavior : Behavior<Entry>
   8:      {
   9:          #region Properties
  10:   
  11:          //Result Boolean
  12:          private static readonly BindablePropertyKey IsValidPropertyKey = BindableProperty.CreateReadOnly("IsValid",
  13:              typeof(bool), typeof(EntryValidatorBehavior), false);
  14:   
  15:          public static readonly BindableProperty IsValidProperty = IsValidPropertyKey.BindableProperty;
  16:   
  17:          public bool IsValid
  18:          {
  19:              get { return (bool)GetValue(IsValidProperty); }
  20:              private set { SetValue(IsValidPropertyKey, value); }
  21:          }
  22:   
  23:          //Result message
  24:          public static readonly BindableProperty MessageProperty = BindableProperty.Create("Message",
  25:              typeof(string), typeof(EntryValidatorBehavior), string.Empty);
  26:   
  27:          public string Message
  28:          {
  29:              get { return (string)GetValue(MessageProperty); }
  30:              private set { SetValue(MessageProperty, value); }
  31:          }
  32:   
  33:          //Is check empty
  34:          public static BindableProperty IsCheckEmptyProperty = BindableProperty.Create("IsCheckEmpty",
  35:              typeof(bool), typeof(EntryValidatorBehavior), false);
  36:   
  37:          public bool IsCheckEmpty
  38:          {
  39:              get { return (bool)GetValue(IsCheckEmptyProperty); }
  40:              set { SetValue(IsCheckEmptyProperty, value); }
  41:          }
  42:   
  43:          //Is check email
  44:          public static BindableProperty IsCheckEmailProperty = BindableProperty.Create("IsCheckEmail",
  45:              typeof(bool), typeof(EntryValidatorBehavior), false);
  46:   
  47:          public bool IsCheckEmail
  48:          {
  49:              get { return (bool)GetValue(IsCheckEmailProperty); }
  50:              set { SetValue(IsCheckEmailProperty, value); }
  51:          }
  52:   
  53:          #endregion Properties
  54:   
  55:          protected override void OnAttachedTo(Entry bindable)
  56:          {
  57:              bindable.TextChanged += HandleTextChanged;
  58:          }
  59:   
  60:          private void HandleTextChanged(object sender, TextChangedEventArgs e)
  61:          {
  62:              if (IsCheckEmpty)
  63:              {
  64:                  IsValid = ValidatorsFactory.IsValidEmpty(e.NewTextValue);
  65:                  Message = Messages.FieldCannotBlank;
  66:                  if (!IsValid)
  67:                  {
  68:                      ((Entry)sender).TextColor = Color.Red;
  69:                      return;
  70:                  }
  71:              }
  72:              if (IsCheckEmail)
  73:              {
  74:                  IsValid = ValidatorsFactory.IsValidEmail(e.NewTextValue);
  75:                  Message = Messages.EmailIncorrectFormat;
  76:                  if (!IsValid)
  77:                  {
  78:                      ((Entry)sender).TextColor = Color.Red;
  79:                      return;
  80:                  }
  81:              }
  82:              //TODO: add more validation
  83:   
  84:              //Default
  85:              IsValid = true;
  86:              Message = string.Empty;
  87:              ((Entry)sender).TextColor = AppColors.EntryTextColor;
  88:          }
  89:   
  90:          protected override void OnDetachingFrom(Entry bindable)
  91:          {
  92:              bindable.TextChanged -= HandleTextChanged;
  93:          }
  94:      }
  95:  }

 * Note: It's contain some constants and reference to my source code so if you just copy&paste, it will not work, let's try to understand it.

Some thing changed here:
 - The first thing I add Message property to provide different validate message of each error for client to display. You see I set value for the Message in HandleTextChanged method.
 - Each kind of validate will have an option to validate it or not. In my code you will see: IsCheckEmpty, IsCheckEmail that you can control what you wanna validate for Entry or not.
 - The HandleTextChanged method ( or handle another event) will do validate and set value for IsValid and Message. You may wonder what is ValidatorsFactory, that the logic of validate I moved to other class call ValidatorsFactory.

Let's see how that factory work:


   1:  using System;
   2:  using System.Text.RegularExpressions;
   3:   
   4:  namespace MyProject.Client.Framework.Validators
   5:  {
   6:      public static class ValidatorsFactory
   7:      {
   8:          private const string EmailRegex =
   9:              @"^(?("")("".+?(?<!\\)""@)|(([0-9a-z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)(?<=[0-9a-z])@))" +
  10:              @"(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-z][-\w]*[0-9a-z]*\.)+[a-z0-9][\-a-z0-9]{0,22}[a-z0-9]))$";
  11:   
  12:          public static bool IsValidEmail(string input)
  13:          {
  14:              return (Regex.IsMatch(input, EmailRegex, RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(250)));
  15:          }
  16:   
  17:          public static bool IsValidEmpty(string input)
  18:          {
  19:              return !string.IsNullOrWhiteSpace(input);
  20:          }
  21:   
  22:          public static bool IsValidMaxlength(string input, int maxlength)
  23:          {
  24:              return input.Length <= maxlength;
  25:          }
  26:   
  27:          //TODO: add more validation
  28:      }
  29:  }
That easy to understand, right? Those static methods will return the valid input or not.

Then, how to use. This example I used in sign-in page:



   1:   <controls:ExtendedEntry Grid.Row="0" Keyboard="Email" Text="{Binding Model.Email, Mode=OneWayToSource}" Placeholder="Email">
   2:          <Entry.Behaviors>
   3:            <validators:EntryValidatorBehavior IsCheckEmpty="True" IsCheckEmail="True" x:Name="EmailValidator"/>
   4:          </Entry.Behaviors>
   5:        </controls:ExtendedEntry>
   6:        <Label Grid.Row="1" TextColor="{x:Static styles:AppColors.LabelErrorTextColor}" 
   7:               Text="{Binding Source={x:Reference EmailValidator}, Path=Message}" 
   8:               VerticalOptions="Start" HorizontalOptions="Start"
   9:               IsVisible="{Binding Source={x:Reference EmailValidator}, Path=IsValid,Converter={StaticResource InvertBoolConverter}}"/>
Here is it:

Thank you for reading.

Edit: Please see the updated version here: http://blog.zquanghoangz.com/?p=139