Xamarin Forms - Usando SQLite com MVVM - I

 Neste artigo vou mostrar como o SQLite em uma aplicação Xamarin Forms usando o padrão MVVM.

Eu já publiquei alguns artigos (veja as referências) sobre como usar o SQLite tanto no Xamarin Android como no Xamarin Forms e estou voltando ao assunto para desta vez mostrar como usar o padrão MVVM em uma aplicação usando o SQLite.

Para saber mais sobre o MVVM veja nas referências os artigos onde eu já tratei dos conceitos e de como usar o MVVM.

Neste artigo eu vou mostrar a maneira correta de criar uma aplicação com acesso a dados usando o MVVM.

Vou criar uma aplicação para gerenciar produtos e vou realizar as operações CRUD para criar, alterar e excluir um produto de uma base de dados local no Sqlite. Para isso vamos usar o plugin sqlite-net-pcl via nuget:  NuGet Gallery | sqlite-net-pcl 1.5.231

Nota:  Existe o plugin  NuGet Gallery | SQLite.Net-PCL 3.1.1  mas este plugin sofreu sua ultima atualização em 2015, e, apresenta erros quando adicionado ao projeto .NET Standard.

A validação de dados será feita usando os recursos do FluentValidation :  NuGet Gallery | FluentValidation 8.0.100

Vamos definir uma camada de acesso aos dados em uma classe DatabaseHelper e implementar o padrão repositório que vai centralizar as operações de acesso e persistência dos dados.

Vamos criar também um conversor de valor (Value Converter) para converter o formato de dados usado no preço do produto para string.

Vamos também aplicar os conceitos de Messaging Service para poder enviar mensagens das ViewModels para as Views, e, do Navigation Service para realizar a navegação entre as páginas usando os recursos de injeção de dependência do Xamarin Forms.

Abaixo temos a aplicação funcionando:

Nossa aplicação vai possui 3 páginas principais que serão definidas via código XAML e definidas pelas views:

  1. PedidoLista.xaml que exibe uma lista dos pedidos;
  2. AddPedido.xaml que permite incluir um novo pedido
  3. DetailsPage.xaml que exibe os detalhes do pedido e permite atualizar e deletar um pedido;

Teremos também uma ContentView chamada PedidoView que vai conter o código comum para Pedidos.

Para cada view vamos definir uma ViewModel  conforme a seguir:

O objetivo é aplicar as boas práticas em nosso projeto de forma a ter uma aplicação robusta e fácil de testar e manter.

  • Recursos Usados

    Criando o projeto no VS 2017

    Abra o  VS 2017 Community e clique em New Project e a seguir escolha Cross Platform -> Mobile App (Xamarin.Forms) e informe o nome PedidoApp:

    A seguir selecione a Plataforma, eu marquei somente Android, e escolha a estratégia de compartilhamento que será .NET Standard.

    Clique no botão OK.

    Pronto nosso projeto já esta criado. ( Atualmente(08/2018) a versão mais atual estável é a 3.1.0)

    Criando a estrutura do projeto

    Para organizar nosso projeto vamos criar as seguintes pastas via menu Project-> New Folder;

    1. Helpers - Pasta onde vamos definir a camada de acesso a dados;
    2. Models - Pasta onde vamos criar o modelo de domínio;   
    3. Services - Pasta onde vamos criar as interfaces e implementações dos servicos de mensagem, navegação e do repositório;
    4. Util - Pasta onde vamos criar a classe de conversão de valor;
    5. Validator - Pasta onde vamos definir a validação usando a FluentValidation;
    6. ViewModels - Pasta onde vamos criar as ViewModels usadas no projeto;
    7. Views - Pasta onde vamos criar as views usadas no projeto;

    A seguir vemos a estrutura criada no projeto:

    Incluindo os pacotes Nuget usados no projeto

    Vamos agora via menu Tools e a opção Manage Nuget Packages for Solution incluir os pacotes nuget usados em nosso projeto:

    Clique na guia Browse e a seguir localize e instale em todos os projetos os seguinte pacotes :

    Ao final deveremos ter as as seguintes dependêncais em nosso projeto .NET Standard :

    Definindo o modelo de domínio

    Vamos definir o nosso modelo de domínio representado pela classe Pedido na pasta Models com o seguinte código:

    Neste código estamos mapeando a entidade Pedido para a tabela Pedidos no banco de dados SQLite, e, definindo o campo Id como chave primária auto-incremental na tabela Pedidos.

    Definindo a camada de acesso a dados

    Vamos definir a camada de acesso a dados criando a classe DatabaseHelper na pasta Helpers.

    Nesta classe vamos definir a conexão com o SQLite e o nome do banco de dados que vamos criar.

    Para simplificar o nosso código vamos usar a biblioteca chamada PCL Storage através da qual podemos usar o compartlhamento de arquivos para todas as plataformas sem ter que escrever código separado para cada plataforma. 

    A PCL Storage fornece um conjunto consistente de APIs IO para arquivos locais para Android, iOS , Windows Store e Windows Phone tornando mais simples o tratamento de arquivos no ambiente multiplataforma.

    Nesta classe vamos definir todos os métodos para realizar o acesso e a persistência dos dados no SQLite.

    Abaixo temos o código da classe DatabaseHelper:

    using PCLExt.FileStorage;
    using PCLExt.FileStorage.Folders;
    using PedidoApp.Models;
    using SQLite;
    using System.Collections.Generic;
    using System.Linq;
    namespace PedidoApp.Helpers
    {
        public class DatabaseHelper
        {
            //defina uma conexao e o  nome do banco de dados
            static SQLiteConnection sqliteconnection;
            public const string DbFileName = "PedidosDB.db";
            public DatabaseHelper()
            {
                //cria uma pasta base local para o dispositivo
                var pasta = new LocalRootFolder();
                //cria o arquivo
                var arquivo = pasta.CreateFile(DbFileName, CreationCollisionOption.OpenIfExists);
                //abre o BD
                sqliteconnection = new SQLiteConnection(arquivo.Path);
                //cria a tabela no BD
                sqliteconnection.CreateTable<Pedido>();
            }
            //Pegar todos os dados  
            public List<Pedido> GetAllPedidosData()
            {
                return (from data in sqliteconnection.Table<Pedido>()
                        select data).ToList();
            }
            //Pegar dados especifico por id
            public Pedido GetPedidoData(int id)
            {
                return sqliteconnection.Table<Pedido>().FirstOrDefault(t => t.Id == id);
            }
            // Deletar todos os dados
            public void DeleteAllPedidos()
            {
                sqliteconnection.DeleteAll<Pedido>();
            }
            // Deletar um dado especifico por id
            public void DeletePedido(int id)
            {
                sqliteconnection.Delete<Pedido>(id);
            }
            // Inserir dados
            public void InsertPedido(Pedido pedido)
            {
                sqliteconnection.Insert(pedido);
            }
            // Atualizar dados
            public void UpdatePedido(Pedido pedido)
            {
                sqliteconnection.Update(pedido);
            }
        }
    }

    Definindo a validação dos dados

    Agora vamos usar os recursos da FluentValidation e definir a validação dos dados em nossa aplicação. Para isso vamos criar classe PedidoValidator na pasta Validator com o código abaixo:

    Estamos validando as propriedades Titulo, Link, Preco e Descricao. Definimos também dois métodos para validação do PrecoPrecoMaiorQueZero, e,  para verificar se a string esta vazia : ValidateStringEmpty.

    Definindo os serviços usados na aplicação

    Na pasta Services vamos criar os serviços usados na aplicação. Para isso vamos definir as interfaces dos serviços e a seguir sua implementação.

    Vamos começar com a definição do repositório usado na aplicação criando a interface IPedidoRepository :

    A seguir crie a classe PedidoRepository que implementa esta interface :

    using PedidoApp.Helpers;
    using PedidoApp.Models;
    using System.Collections.Generic;
    namespace PedidoApp.Services
    {
        //implementa o repositorio
        public class PedidoRepository : IPedidoRepository
        {
            DatabaseHelper _databaseHelper;
            public PedidoRepository()
            {
                _databaseHelper = new DatabaseHelper();
            }
            public void DeleteAllPedidos()
            {
                _databaseHelper.DeleteAllPedidos();
            }
            public void DeletePedido(int pedidoID)
            {
                _databaseHelper.DeletePedido(pedidoID);
            }
            public List<Pedido> GetAllPedidosData()
            {
                return _databaseHelper.GetAllPedidosData();
            }
            public Pedido GetPedidoData(int pedidoID)
            {
                return _databaseHelper.GetPedidoData(pedidoID);
            }
            public void InsertPedido(Pedido pedido)
            {
                _databaseHelper.InsertPedido(pedido);
            }
            public void UpdatePedido(Pedido pedido)
            {
                _databaseHelper.UpdatePedido(pedido);
            }
        }
    }

    Nesta implementação do padrão repositório estamos usando uma instância da nossa camada de acesso a dados para ter acesso e persistir os dados. Centralizamos assim nesta classe todo o acesso a dados.

    Agora vamos definir o serviço de mensagens usado na aplicação. Crie a interface IMessageService com o código abaixo:

    Esses métodos serão usados para exibir mensagens usando a classe DisplayAlert. Para implementar esta interface crie a classe MessageService :

    A implementação obtém a página atual do topo da pilha de navegação da aplicação : PedidoApp.App.Current e usa a classe DisplayAlert() para exibir uma mensagem.

    Para concluir vamos definir o serviço de navegação criando a interface INavigationService :

    Na interface definimos os métodos para navegar entre as páginas da aplicação.

    Na classe NavigationService vamos implementar esta interface conforme a seguir:

    Definimos a implementação dos métodos:

    Para concluir essa etapa vamos criar na pasta Util a classe CurrencyConverter cujo código temos a seguir:

    using System;
    using System.Globalization;
    using System.Text.RegularExpressions;
    using Xamarin.Forms;
    namespace PedidoApp.Util
    {
        /// <summary>
        /// Conversor para ser usado em campos Entry com máscara para moeda
        /// <para>A propriedade vinculada deve ser to tipodecimal, e tem que invocar a
        /// o evento PropertyChangedEventArgs sempre que seu valor for alterado
        /// de forma que o comportamento da mascara seja mantido.</para>
        /// </summary>
        public class CurrencyConverter : IValueConverter
        {
            public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
            {
                //implementação para notação 000.00
                //return Decimal.Parse(value.ToString()).ToString("C");
                //implementação local pt-br 000,00
                NumberFormatInfo nfi = new CultureInfo("pt-BR").NumberFormat;
                return Decimal.Parse(value.ToString()).ToString("C", nfi);
            }
            public object ConvertBack(object value,Type targetType,object parameter,CultureInfo culture)
            {
                string valueFromString = Regex.Replace(value.ToString(), @"\D", "");
                if (valueFromString.Length <= 0)
                    return 0m;
                long valueLong;
                if (!long.TryParse(valueFromString, out valueLong))
                    return 0m;
                if (valueLong <= 0)
                    return 0m;
                return valueLong / 100m;
            }
        }
    }

    Nessa classe convertemos os valores do preço do pedido informados via view Entry para o formato string onde implementamos a notação da moeda Real (R$).

    Na próxima parte to artigo vamos registrar os serviços e definir as Views e as ViewModels da nossa aplicação.

    "Jesus respondeu, e disse-lhe: Se alguém me ama, guardará a minha palavra, e meu Pai o amará, e viremos para ele, e faremos nele morada."
    João 14:23

    Referências:


    José Carlos Macoratti