Syncfusion para dummies en Xamarin.Forms

Para esta entrada como parte del #AdvientoXamarin he pensado en dar una pequeña reseña del cómo podemos implementar los controles de Syncfusion, para los que estamos en el área estos controles dan un gran impacto visual a nuestras apps, tienes que saber que Syncfusion ofrece una licencia completamente gratuita para empresas o developers para usar sus productos siempre y cuando cumplas lo siguientes requisitos:
  1. Si obtienes menos de 1 millón de dólares al año y un equipo de 5 o menos developers
Así que comencemos aquí habrá que realizar el registro hasta tener acceso a la consola (tendrás que dar tu número telefónico ya que te hacen una llamada).
1- Obtener la llave de nuestra licencia
Este paso es muy importante para evitar el mensaje de aviso, al inicio de nuestra app, les dejo una serie de imágenes para que vean el proceso de obtener la clave de licencia.

En esta pantalla es necesario seleccionar por ahora Xamarin


Guarda muy bien tu clave, y aprovechando copiala a tu portapapeles

2- Creación del proyecto y configuración con la clave
Pasemos a crear un proyecto Cross platform en visual studio (por ahora omitimos este proceso ya que es relativamente simple)

Syncfusion cuenta con una gran gama de controles las cuales puedes encontrar aquí cada control cuenta con su respectiva documentación desde la instalación hasta su integración. 

Aquí nosotros veremos la integración de un par de ellos y claro usando buenas prácticas como:
  • MVVM (sin framework)
  • Clean code
  • Entre otras buenas practicas (documentación de código, uso de regiones)
3- Integración de un combo con autocompletado
Accede a los nugets de la solución y busca  Syncfusion.Xamarin.SfAutoComplete tendrás que instalarlo en todos tus proyectos (android, ios) en nuestro caso el proyecto solo abarca android como plataforma de alcance, con esta instalación también se instalan las referencias necesarias.

  
Antes de continuar... quiero comentarte que nosotros estaremos creando los controles por medio de XAML (no del backend) en mi experiencia procuro no utilizar el código detrás de XAML, eso a criterio de cada uno de nosotros como programadores.
Ahora si continuemos :)

4- Preparación del proyecto con la llave de nuestra cuenta de syncfusion
Habrá que establecer el licenciamiento de nuestro proyecto para poder utilizar los controles de Syncfusion, para ello en el constructor de tu clase App.xaml.cs deberás establecer el siguiente código:


Syncfusion.Licensing.SyncfusionLicenseProvider.RegisterLicense("KEY DE SYNCFUSION");

       

Además es necesario que actualices tus nugets, y como extra que tu proyecto android apunte a la última versión de la api de android en mi caso Android 9 - Pie

5- Comencemos con la interfaz gráfica 
Que les parece si creamos una vista que implemente un perfil de usuario, para ello manejaremos algunos controles de syncfusion los cuales son:

Todos los controles los puedes encontrar aquí en caso de que quieras implementar alguno más, sé que son pocos pero la idea es darte la intro a syncfusion y que sepas configurarlo para usar los controles.

Nuestra interfaz nos quedaría así, en la vista MainPage que viene por default en el proyecto:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
    x:Class="XamarinAdvientoSyncfusion.MainPage"
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:autocomplete="clr-namespace:Syncfusion.SfAutoComplete.XForms;assembly=Syncfusion.SfAutoComplete.XForms"
    xmlns:avatarView="clr-namespace:Syncfusion.XForms.AvatarView;assembly=Syncfusion.Core.XForms"
    xmlns:buttons="clr-namespace:Syncfusion.XForms.Buttons;assembly=Syncfusion.Buttons.XForms"
    xmlns:popupLayout="clr-namespace:Syncfusion.XForms.PopupLayout;assembly=Syncfusion.SfPopupLayout.XForms"
    xmlns:textInputLayout="clr-namespace:Syncfusion.XForms.TextInputLayout;assembly=Syncfusion.Core.XForms"
    Title="Mi perfil">
    <ContentPage.ToolbarItems>
        <ToolbarItem Command="{Binding GuardarPerfilCommand}" Text="Guardar" />
    </ContentPage.ToolbarItems>
    <ScrollView HorizontalOptions="FillAndExpand" VerticalOptions="FillAndExpand">
        <StackLayout
            Padding="20"
            HorizontalOptions="FillAndExpand"
            VerticalOptions="FillAndExpand">
            <avatarView:SfAvatarView
                Margin="0,15,0,0"
                ContentType="Custom"
                CornerRadius="150"
                HeightRequest="250"
                HorizontalOptions="Center"
                ImageSource="stock.jpg"
                VerticalOptions="Center"
                WidthRequest="250" />
            <textInputLayout:SfTextInputLayout
                ContainerType="None"
                ErrorText="{Binding Usuario.MensajeErrorUsuario}"
                HasError="{Binding Usuario.ErrorUsuario}"
                HelperText="Captura tu(s) nombre(s)"
                Hint="Nombre(s)"
                ReserveSpaceForAssistiveLabels="True">
                <Entry Text="{Binding Usuario.Nombre, Mode=TwoWay}" />
            </textInputLayout:SfTextInputLayout>
            <textInputLayout:SfTextInputLayout
                ContainerType="None"
                ErrorText="{Binding Usuario.MensajeErrorApellidos}"
                HasError="{Binding Usuario.ErrorApellidos}"
                HelperText="Captura tus apellidos"
                Hint="Apellido(s)"
                ReserveSpaceForAssistiveLabels="True">
                <Entry Text="{Binding Usuario.Apellidos, Mode=TwoWay}" />
            </textInputLayout:SfTextInputLayout>
            <textInputLayout:SfTextInputLayout
                ContainerType="None"
                ErrorText="{Binding Usuario.MensajeErrorNick}"
                HasError="{Binding Usuario.ErrorNick}"
                HelperText="¿Cual será tu nombre clave?"
                Hint="Nick"
                ReserveSpaceForAssistiveLabels="True">
                <Entry Text="{Binding Usuario.Nick, Mode=TwoWay}" />
            </textInputLayout:SfTextInputLayout>
            <textInputLayout:SfTextInputLayout
                CharMaxLength="8"
                ContainerType="None"
                EnablePasswordVisibilityToggle="True"
                ErrorText="{Binding Usuario.MensajeErrorPass}"
                HasError="{Binding Usuario.ErrorPass}"
                HelperText="Captura tu contraseña"
                Hint="Contraseña"
                ReserveSpaceForAssistiveLabels="True"
                ShowCharCount="true">
                <Entry Text="{Binding Usuario.Pass, Mode=TwoWay}" />
            </textInputLayout:SfTextInputLayout>
            <autocomplete:SfAutoComplete
                AutoCompleteMode="Suggest"
                ClearButtonColor="Black"
                DataSource="{Binding ListaEstados}"
                DisplayMemberPath="Nombre"
                DropDownItemHeight="35"
                HeightRequest="45"
                HighlightedTextColor="Red"
                IsFocused="True"
                MaximumDropDownHeight="150"
                MinimumPrefixCharacters="1"
                NoResultsFoundText="Sin coincidencias"
                PopupDelay="0"
                SelectedItem="{Binding Usuario.Estado}"
                ShowClearButton="True"
                SuggestionMode="Contains"
                Text="{Binding Usuario.Estado, Mode=TwoWay}"
                TextColor="Black"
                TextHighlightMode="FirstOccurrence"
                TextSize="16"
                Watermark="Buscar estado..."
                WatermarkColor="#adb2bb" />
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="70*" />
                    <ColumnDefinition Width="30*" />
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition Height="*" />
                </Grid.RowDefinitions>
                <textInputLayout:SfTextInputLayout
                    Grid.Column="0"
                    ContainerType="None"
                    ErrorText="{Binding MensajeErrorHobbie}"
                    HasError="{Binding ErrorHobbie}"
                    HelperText="Captura un hobbie"
                    Hint="Este es uno de mis hobbies"
                    ReserveSpaceForAssistiveLabels="True">
                    <Entry Text="{Binding NuevoHobbie, Mode=TwoWay}" />
                </textInputLayout:SfTextInputLayout>
                <buttons:SfButton
                    Grid.Column="1"
                    Command="{Binding AgregarHobbieCommand}"
                    Text="Agregar" />
            </Grid>
            <buttons:SfChipGroup DisplayMemberPath="Nombre" ItemsSource="{Binding Usuario.Hobbies}">
                <buttons:SfChipGroup.ChipLayout>
                    <FlexLayout
                        AlignContent="Start"
                        AlignItems="Start"
                        Direction="Row"
                        HorizontalOptions="Start"
                        JustifyContent="Start"
                        VerticalOptions="Center"
                        Wrap="Wrap" />
                </buttons:SfChipGroup.ChipLayout>
            </buttons:SfChipGroup>
            <popupLayout:SfPopupLayout IsOpen="{Binding IsSuccess}">
                <popupLayout:SfPopupLayout.PopupView>
                    <popupLayout:PopupView
                        AnimationMode="Zoom"
                        ShowFooter="False" 
                        AutoSizeMode="Height"
                        ShowHeader="False">
                        <popupLayout:PopupView.ContentTemplate>
                            <DataTemplate>
                                <Label
                                    Padding="15"
                                    BackgroundColor="White"
                                    HorizontalOptions="FillAndExpand"
                                    Text="Los datos fueron registrados con éxito" />
                            </DataTemplate>
                        </popupLayout:PopupView.ContentTemplate>
                    </popupLayout:PopupView>
                </popupLayout:SfPopupLayout.PopupView>
            </popupLayout:SfPopupLayout>
        </StackLayout>
    </ScrollView>
</ContentPage>

En este código estamos usando MVVM con un objeto principal para la transferencia de información mejor conocido como Dto con el cual pretendemos enviar toda la información capturada en el perfil del usuario (este Dto implementa la interfaz INotifyPropertyChanged), la imagen es representativa y esta adjunta directamente en la carpeta Drawable, usamos propiedades para manejar los errores de captura al momento de guardar.
Muy importante, recuerda hacer la parte del binding en el código detrás de la vista.


using Xamarin.Forms;
using XamarinAdvientoSyncfusion.ViewModels;

namespace XamarinAdvientoSyncfusion
{
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();
            BindingContext = new PerfilUsuarioViewModel();
        }
    }
}    

Ahora pasemos al código ViewModel, en donde de entrada también aplicamos la interfaz INotifyPropertyChanged, para mediante algunas propiedades manejar el comportamiento de ciertos controles, no olvides manejar regiones para un código más limpio.


using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Input;
using Xamarin.Forms;
using XamarinAdvientoSyncfusion.Dtos;

namespace XamarinAdvientoSyncfusion.ViewModels
{
    public class PerfilUsuarioViewModel:INotifyPropertyChanged
    {
        #region VariablesObjetos

        private DtoHobbie hobbie;
        private DtoUsuario usuario=new DtoUsuario();

        public DtoUsuario Usuario
        {
            get { return usuario; }
            set { OnSetProperty("Usuario"); }
        }
        private string nuevoHobbie;

        public string NuevoHobbie
        {
            get { return nuevoHobbie; }
            set
            {
                nuevoHobbie = value;
                OnSetProperty("NuevoHobbie");
            }
        }

        private bool isSuccess;

        public bool IsSuccess
        {
            get { return isSuccess; }
            set
            {
                isSuccess = value; OnSetProperty("IsSuccess"); }
        }
        private string mensajeErrorHobbie;

        public string MensajeErrorHobbie
        {
            get { return mensajeErrorHobbie; }
            set { OnSetProperty("MensajeErrorHobbie"); }
        }
        private bool errorHobbie;

        public bool ErrorHobbie
        {
            get { return errorHobbie; }
            set { OnSetProperty("ErrorHobbie"); }
        }
        public ICommand AgregarHobbieCommand { get; set; }
        public ICommand GuardarPerfilCommand { get; set; }
        public ObservableCollection ListaEstados { get; set; }
        #endregion
        #region Metodos
        public PerfilUsuarioViewModel()
        {
            LlenarEstadosCiudades();
            AgregarHobbieCommand=new Command(AgregarHobbie);
            GuardarPerfilCommand = new Command(GuardarPerfil);
        }

        private void GuardarPerfil()
        {
            if (ValidarDatos())
            {
                IsSuccess = true;
            }
        }

        private bool ValidarDatos()
        {
            bool datosValidos=true;
            if (string.IsNullOrWhiteSpace(Usuario.Nombre))
            {
                Usuario.ErrorUsuario = true;
                Usuario.MensajeErrorUsuario = "Nombre, es obligatorio";
                datosValidos = false;
            }
            else
            {
                Usuario.ErrorUsuario = false;
                Usuario.MensajeErrorUsuario = "";
            }
            if (string.IsNullOrWhiteSpace(Usuario.Apellidos))
            {
                Usuario.ErrorApellidos = true;
                Usuario.MensajeErrorApellidos = "Apellidos, es obligatorio";
                datosValidos = false;
            }
            else
            {
                Usuario.ErrorApellidos = false;
                Usuario.MensajeErrorApellidos = "";
            }
            if (string.IsNullOrWhiteSpace(Usuario.Nick))
            {
                Usuario.ErrorNick = true;
                Usuario.MensajeErrorNick = "Apellidos, es obligatorio";
                datosValidos = false;
            }
            else
            {
                Usuario.ErrorNick = false;
                Usuario.MensajeErrorNick = "";
            }
            if (string.IsNullOrWhiteSpace(Usuario.Pass))
            {
                Usuario.ErrorPass = true;
                Usuario.MensajeErrorPass = "Apellidos, es obligatorio";
                datosValidos = false;
            }
            else
            {
                Usuario.ErrorPass = false;
                Usuario.MensajeErrorPass = "";
            }

            return datosValidos;
        }

        private void AgregarHobbie()
        {
            if (!string.IsNullOrEmpty(nuevoHobbie))
            {
                MensajeErrorHobbie = "";
                ErrorHobbie = false;                
                usuario.Hobbies.Add(new DtoHobbie { Nombre = nuevoHobbie });
                NuevoHobbie = "";
            }
            else
            {
                MensajeErrorHobbie = "No agregaste un hobbie";
                ErrorHobbie = true;
            }
        }

        private void LlenarEstadosCiudades()
        {
            ListaEstados=new ObservableCollection
            {
                "Guanajuato","Guerrero","Hidalgo","Jalisco","México","Michoacán","Morelos","Nayarit","Nuevo León","Oaxaca","Puebla"
            };
        }

        #endregion
        #region Eventos INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;

        public void OnSetProperty(string property)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(property));
            }
        }
        #endregion
    }
}

El Dto de usuario que tendría las siguientes propiedades


using System.Collections.ObjectModel;
using System.ComponentModel;

namespace XamarinAdvientoSyncfusion.Dtos
{
    public class DtoUsuario: INotifyPropertyChanged
    {
        #region PropiedadesUsuario
        private string estado;

        public string Estado
        {
            get { return estado; }
            set { estado = value; OnSetProperty("Estado"); }
        }

        private string nombre;

        public string Nombre
        {
            get { return nombre; }
            set { nombre = value; OnSetProperty("Nombre"); }
        }
        private string apellidos;

        public string Apellidos
        {
            get { return apellidos; }
            set { apellidos = value; OnSetProperty("Apellidos"); }
        }

        private string nick;

        public string Nick
        {
            get { return nick; }
            set { nick = value; OnSetProperty("Nick"); }
        }
        private string pass;

        public string Pass
        {
            get { return pass; }
            set { pass = value; OnSetProperty("Pass"); }
        }

        private ObservableCollection hobbies=new ObservableCollection();

        public ObservableCollection Hobbies
        {
            get { return hobbies; }
            set
            {hobbies = value; OnSetProperty("Hobbies"); }
        }

        #endregion

        #region ManejoDeErrores

        private string mensajeErrorUsuario;

        public string MensajeErrorUsuario
        {
            get { return mensajeErrorUsuario; }
            set { mensajeErrorUsuario = value; OnSetProperty("MensajeErrorUsuario"); }
        }

        private bool errorUsuario;

        public bool ErrorUsuario
        {
            get { return errorUsuario; }
            set { errorUsuario = value; OnSetProperty("ErrorUsuario"); }
        }
        private string mensajeErrorApellidos;

        public string MensajeErrorApellidos
        {
            get { return mensajeErrorUsuario; }
            set { mensajeErrorUsuario = value; OnSetProperty("MensajeErrorApellidos"); }
        }

        private bool errorApellidos;

        public bool ErrorApellidos
        {
            get { return errorApellidos; }
            set { errorApellidos = value; OnSetProperty("ErrorApellidos"); }
        }
        private string mensajeErrorNick;

        public string MensajeErrorNick
        {
            get { return mensajeErrorNick; }
            set { mensajeErrorNick = value; OnSetProperty("MensajeErrorNick"); }
        }

        private bool errorNick;

        public bool ErrorNick
        {
            get { return errorNick; }
            set { errorNick = value; OnSetProperty("ErrorNick"); }
        }
        private string mensajeErrorPass;

        public string MensajeErrorPass
        {
            get { return mensajeErrorPass; }
            set { mensajeErrorPass = value; OnSetProperty("MensajeErrorPass"); }
        }

        private bool errorPass;

        public bool ErrorPass
        {
            get { return errorPass; }
            set { errorPass = value; OnSetProperty("ErrorPass"); }
        }
        #endregion

        #region Eventos INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;

        public void OnSetProperty(string property)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(property));
            }
        }
        #endregion
    }
}   

Ya para concluir, el resultado que podrás ver es algo parecido a esto


 Todo este código lo podrás encontrar en mi repo de Git, así cerramos esta entrada espero que te sea útil la información. Cualquier duda puedes dejarla en el apartado de comentario, estaré pendiente si puedo ayudar en algo :)

Comentarios

Entradas populares de este blog

Generando pases para wallet (iOS, Android) con .NET y C#

Política de privacidad un simple developer

Clean code, ¿Qué es? ¿Cómo usarlo?