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

8 comments:

  1. This looks fantastic! I'm going to try and implement this now :)

    ReplyDelete
  2. Replies
    1. A static class that define string constants of messages.

      Delete
    2. ya got it thanx ..have used your code..if you have any sample of editable entry inside listview then please upload
      thanx in advance :-)

      Delete
  3. Hi, namespace controls and validators prefix not defined showing on my xaml file, how to solve it. I am new at xamarin.

    ReplyDelete
  4. Also when I build my project Messages and AppColors doesn't exist error is getting here, how to resolve it?

    ReplyDelete
  5. Hi, it would be really great if you can share the source code.

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

    ReplyDelete