André Alves de Lima

Talking about Software Development and more…

Aplicações Android com Xamarin – parte 7 de N – SQLite no Xamarin Android

Chegou o dia de continuar com a minha série sobre desenvolvimento Android com Xamarin Native! No artigo de hoje falaremos sobre um tema muito, mas muito interessante (não que os outros não tenham sido). O que seria das nossas aplicações sem o armazenamento de informações? Praticamente qualquer aplicação precisa armazenar algum tipo de informação. Hoje eu vou mostrar para você como armazenar informações em um banco de dados SQLite no Xamarin Android.

Acesse os artigos anteriores

Antes de continuar com a leitura deste artigo, acesse os artigos anteriores dessa série utilizando os links abaixo:

Parte 1: Prototipando a aplicação
Parte 2: Ambiente Xamarin Android e Hello World
Parte 3: Classes de modelo e primeira tela
Parte 4: Customizando o controle ListView
Parte 5: Navegação entre telas
Parte 6: DatePicker e Spinner

Armazenamento de informações no Xamarin Android

Quando falamos de desenvolvimento Android com Xamarin Native, existem algumas maneiras de armazenarmos informações no dispositivo. Primeiramente, podemos utilizar as preferências da aplicação para armazenarmos combinações de chave/valor. Com poucas linhas de código conseguimos armazenar e recuperar informações das preferências da aplicação, porém, isso é um tanto quanto limitado, uma vez que só conseguimos trabalhar com combinações de chave/valor.

Se quisermos armazenar informações mais complexas, temos que partir para a criação de arquivos. Nessa categoria de armazenamento de dados, podemos criar arquivos texto, o que não é recomendado, já que esse tipo de arquivo não fornece estrutura nenhuma e teríamos que fazer o “parse” deles manualmente.

Uma opção um pouco melhor seria o armazenamento em arquivos mais estruturados, como XML ou JSON. Como temos acesso às classes base do .NET no Xamarin, nós podemos facilmente criar arquivos desse tipo nas nossas aplicações móveis também.

Porém, quando a estrutura das informações começa a ficar mais complexa, essa sistemática começará a apresentar alguns problemas. Por exemplo, como faremos se precisarmos armazenar informações com diferentes tipos de estrutura (diferentes tabelas)? Poderíamos salvar todas as informações em um único arquivo XML ou JSON, mas aí o arquivo começa a virar uma salada.

Nesse caso, não temos saída. O jeito é utilizar um banco de dados local para salvarmos as informações das nossas aplicações. Mas, qual banco de dados local nós temos à nossa disposição no Xamarin? Se pensarmos em bancos de dados locais no .NET “completo” (desktop/web), algumas opções podem vir em mente, como Microsoft Access, SQL CE, LocalDB e SQLite. Qual é a opção mais indicada no Xamarin? Como você já notou pelo título desse artigo, a opção mais fácil nesse caso é o SQLite!

Tanto o Android como o iOS possuem suporte nativo ao SQLite (em seus respectivos SDKs). Portanto, obviamente nós conseguimos utilizar o SQLite no Xamarin Android também.

Nas próximas seções nós veremos 4 maneiras de armazenarmos informações na nossa aplicação de controle de gastos. Primeiro nós veremos como salvar informações nas preferências da aplicação. Depois nós veremos como nós podemos criar um arquivo JSON. Por fim, nós aprenderemos duas maneiras de trabalharmos com SQLite no Xamarin Android (com ADO.NET e com a biblioteca SQLite.Net).

Salvando combinações de chave/valor nas preferências da aplicação

Como mencionei anteriormente, no Android nós conseguimos armazenar combinações de chave/valor nas preferências da aplicação. Isso é muito útil para armazenarmos configurações da aplicação. Como a nossa aplicação de controle de gastos é muito simples, nós não temos nenhuma configuração que nós poderíamos salvar nas preferências da aplicação. Porém, só para entendermos essa funcionalidade, pensei em salvarmos o ID do último estabelecimento utilizado, de forma que ele seja automaticamente selecionado quando criamos novos gastos.

O armazenamento de informações nas preferências da aplicação é realizado pelo Android através da interface ISharedPreferences. Para utilizarmos essa interface, nós temos que chamar o método “GetDefaultSharedPreferences” passando o contexto desejado (por exemplo, o contexto geral da aplicação, que conseguimos acessar através da propriedade Application.Context). Uma vez que nós tivermos uma instância de SharedPreferences, nós podemos salvar valores novos ou recuperar valores que foram armazenados anteriormente.

Para salvarmos (ou atualizarmos) uma informação nova nas preferências, nós temos que chamar o método “Edit“, que retornará um editor de preferências. Com esse editor, nós conseguimos utilizar os métodos que começam com “Put” (PutInt, PutString, etc) para armazenarmos novas informações. Uma vez realizadas as alterações, nós chamamos o método “Commit” para realmente salvarmos as preferências.

Veja só como é que fica o código para salvarmos o último estabelecimento que foi selecionado na nossa aplicação (esse código deve ser inserido no método “Salvar_Click” da classe “EditarGastoActivity“):

var estabelecimento = MainActivity.Estabelecimentos[_spinnerEstabelecimento.SelectedItemPosition];
intent.PutExtra("Estabelecimento", estabelecimento.Nome);
var sharedPreferences = Android.Preferences.PreferenceManager.GetDefaultSharedPreferences(Application.Context);
var preferencesEditor = sharedPreferences.Edit();
preferencesEditor.PutInt("UltimoEstabelecimento", estabelecimento.Id);
preferencesEditor.Commit();

A recuperação de informações das preferências é ainda mais simples. Através dos métodos que começam com “Get” (GetInt, GetString, etc) passando o nome da chave a ser recuperada, nós conseguimos acessar os valores que tiverem sido armazenados anteriormente. Todos os métodos “Get” possuem um segundo parâmetro que representa o valor padrão que deve ser retornado caso a chave não seja encontrada.

Veja como fica o código para recuperarmos o ID do estabelecimento que foi armazenado anteriormente (esse código deve ser inserido no método “OnCreate” da classe “EditarGastoActivity“):

var estabelecimento = MainActivity.Estabelecimentos.FirstOrDefault(e => nomeEstabelecimento == e.Nome);
if (estabelecimento == null)
{
    var sharedPreferences = Android.Preferences.PreferenceManager.GetDefaultSharedPreferences(Application.Context);
    var estabelecimentoID = sharedPreferences.GetInt("UltimoEstabelecimento", -1);
    if (estabelecimentoID != -1)
    {
        estabelecimento = MainActivity.Estabelecimentos.FirstOrDefault(e => e.Id == estabelecimentoID);
    }
}

Salvando informações no formato JSON

A partir do momento em que temos que salvar dados mais complexos, as preferências acabam não sendo o local de armazenamento mais ideal. Esse é o caso da nossa lista de estabelecimentos e gastos. Nesse caso, partiremos para o armazenamento em arquivo.

O primeiro formato que eu quero mostrar para você é o JSON. Esse formato é muito utilizado hoje em dia, principalmente quando transferimos dados através de uma API. O formato XML ainda é muito utilizado também, mas vejo um movimento de transição muito forte para o formato JSON no que diz respeito ao armazenamento de informações em arquivos.

Nota: antes de continuarmos, é importante que você dê uma olhada neste commit que eu fiz no projeto de controle de gastos. Eu extraí tudo relacionado ao armazenamento de dados em uma classe (chamada “Dados”). Anteriormente tudo isso estava sendo feito diretamente na Activity. O código fica muito melhor com uma classe separada somente para essa responsabilidade.

Existem diversas bibliotecas que implementam a serialização e “parse” de JSON no .NET. Eu até já escrevi um artigo sobre isso, caso você tenha interesse. Primeiro eu pensei em utilizar a biblioteca Json.NET (Newtonsoft), que é a mais conhecida. Entretanto, quando fui adicionar a referência pelo NuGet, vi que ele ia adicionar trocentas mil referências extras (por ser um projeto Xamarin), aí decidi utilizar a classe DataContractJsonSerializer mesmo (que já está disponível nativamente no projeto Mono).

Primeiramente, temos que adicionar uma referência para o assembly “System.Runtime.Serialization“:

Feito isso, nós teremos acesso à classe DataContractJsonSerializer. Para salvarmos o estado atual dos estabelecimentos e gastos, nós só temos que criar um “Serializer” e, em seguida, chamamos o método “WriteObject” passando o objeto que deverá ser salvo (no nosso caso, a lista de estabelecimentos e a lista de gastos). Veja como fica o código:

public void Salvar()
{
    var diretorio = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);

    using (var stream = new System.IO.FileStream(System.IO.Path.Combine(diretorio, "estabelecimentos.json"), System.IO.FileMode.Create))
    {
        var serializer = new System.Runtime.Serialization.Json.DataContractJsonSerializer(typeof(List<Models.Estabelecimento>));
        serializer.WriteObject(stream, Estabelecimentos);
    }
    using (var stream = new System.IO.FileStream(System.IO.Path.Combine(diretorio, "gastos.json"), System.IO.FileMode.Create))
    {
        var serializer = new System.Runtime.Serialization.Json.DataContractJsonSerializer(typeof(List<Models.Gasto>));
        serializer.WriteObject(stream, Gastos);
    }
}

Esse método “Salvar” deve ser chamado sempre que quisermos salvar as informações atuais no arquivo (no nosso caso, temos que adicionar essa chamada no final do método “OnActivityResult” da classe “MainActivity“).

O carregamento das informações que foram previamente salvas segue o mesmo princípio. Porém, ao invés de chamarmos o método “WriteObject“, nós chamaremos o método “ReadObject“:

public void Carregar()
{
    Estabelecimentos.Clear();
    Gastos.Clear();

    var diretorio = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);

    var caminhoEstabelecimentos = System.IO.Path.Combine(diretorio, "estabelecimentos.json");
    if (System.IO.File.Exists(caminhoEstabelecimentos))
    {
        using (var stream = new System.IO.FileStream(caminhoEstabelecimentos, System.IO.FileMode.Open))
        {
            var serializer = new System.Runtime.Serialization.Json.DataContractJsonSerializer(typeof(List<Models.Estabelecimento>));
            Estabelecimentos = (List<Models.Estabelecimento>)serializer.ReadObject(stream);
        }
    }
    else
    {
        for (int c = 1; c <= 10; c++)
        {
            Estabelecimentos.Add(new Models.Estabelecimento() { Id = c, Nome = string.Format("Estabelecimento {0}", c) });
        }
    }

    var caminhoGastos = System.IO.Path.Combine(diretorio, "gastos.json");
    if (System.IO.File.Exists(caminhoGastos))
    {
        using (var stream = new System.IO.FileStream(caminhoGastos, System.IO.FileMode.Open))
        {
            var serializer = new System.Runtime.Serialization.Json.DataContractJsonSerializer(typeof(List<Models.Gasto>));
            Gastos = (List<Models.Gasto>)serializer.ReadObject(stream);
        }
    }
}

Nota: perceba que estamos adicionando os 10 estabelecimentos “padrão” caso o arquivo “estabelecimentos.json” ainda não exista. Só estamos fazendo isso porque nós ainda não implementamos uma maneira de criarmos novos estabelecimentos diretamente na nossa aplicação. Isso será resolvido mais para a frente nessa série.

Pronto! E assim implementamos o esquema de salvar e recuperar os estabelecimentos e gastos utilizando arquivos JSON.

SQLite no Xamarin Android com ADO.NET

Se quisermos partir para um armazenamento de dados mais robusto, temos que introduzir um banco de dados na jogada. Como tanto a plataforma Android quanto a plataforma iOS possui suporte nativo ao SQLite no Xamarin, ele acaba sendo a primeira opção que vem à mente.

Quando falamos de SQLite no Xamarin, nós temos basicamente duas opções. A primeira delas é trabalharmos diretamente com o provider ADO.NET do SQLite, que está disponível nativamente no projeto Mono. Para utilizarmos esse provider, nós primeiramente temos que adicionar duas referências no nosso projeto (System.Data e System.Data.SQLite):

Feito isso, nós teremos acesso às classes do ADO.NET relacionadas ao SQLite (como SqliteConnection, SqliteCommand, etc). Antes de iniciarmos com a implementação, vamos criar um novo atributo chamado “_caminhoBanco” dentro da classe “Dados“. Esse atributo, como o próprio nome já diz, armazenará o caminho para o banco de dados (que será um arquivo chamado “gastos.db” na pasta pessoal do dispositivo):

public List<Models.Estabelecimento> Estabelecimentos { get; private set; }
public List<Models.Gasto> Gastos { get; private set; }
private string _caminhoBanco;

public Dados()
{
    Estabelecimentos = new List<Models.Estabelecimento>();
    Gastos = new List<Models.Gasto>();
    _caminhoBanco = System.IO.Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal), "gastos.db");
}

Em seguida, nós só temos que utilizar o bom e velho ADO.NET para executarmos sentenças SQL nesse banco. Se você tiver dificuldades nessa área, recomendo que você dê uma conferida primeiro no meu artigo básico sobre SQLite no C# e VB.NET. O código para salvarmos a lista de gastos no banco ficaria assim:

public void Salvar()
{
    using (var conn = new Mono.Data.Sqlite.SqliteConnection("Data Source=" + _caminhoBanco))
    {
        conn.Open();
        if (conn.State == System.Data.ConnectionState.Open)
        {
            using (var comm = new Mono.Data.Sqlite.SqliteCommand(conn))
            {
                comm.CommandText = "DELETE FROM Gasto";
                comm.ExecuteNonQuery();

                comm.CommandText = "INSERT INTO Gasto (Data, EstabelecimentoId, Valor) VALUES (@Data, @EstabelecimentoID, @Valor)";
                foreach (var gasto in Gastos)
                {
                    comm.Parameters.Clear();
                    comm.Parameters.AddWithValue("@Data", gasto.Data);
                    comm.Parameters.AddWithValue("@EstabelecimentoId", gasto.Estabelecimento.Id);
                    comm.Parameters.AddWithValue("@Valor", gasto.Valor);
                    comm.ExecuteNonQuery();
                }
            }
        }
    }
}

Note que nós só estamos limpando a tabela de gastos e executando um comando de INSERT para inserir os gastos novamente com base nas informações atuais da lista de gastos. Mas, onde é que está essa tabela “Gastos“? Nós não criamos essa tabela no banco ainda. Pois bem, vamos adicionar um novo método nessa classe que será o responsável por fazer a criação do banco vazio (comandos “CREATE TABLE“):

private void CriarBancoVazio()
{
    Mono.Data.Sqlite.SqliteConnection.CreateFile(_caminhoBanco);
    using (var conn = new Mono.Data.Sqlite.SqliteConnection("Data Source=" + _caminhoBanco))
    {
        conn.Open();
        if (conn.State == System.Data.ConnectionState.Open)
        {
            using (var comm = new Mono.Data.Sqlite.SqliteCommand(conn))
            {
                comm.CommandText = "CREATE TABLE Estabelecimento (Id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, Nome TEXT NOT NULL)";
                comm.ExecuteNonQuery();

                for (int c = 1; c <= 10; c++)
                {
                    comm.CommandText = "INSERT INTO Estabelecimento (Nome) VALUES (@Nome)";
                    comm.Parameters.Clear();
                    comm.Parameters.AddWithValue("@Nome", string.Format("Estabelecimento {0}", c));
                    comm.ExecuteNonQuery();
                }

                comm.CommandText = "CREATE TABLE Gasto (Id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, Data TEXT NOT NULL, EstabelecimentoId INTEGER NOT NULL, Valor REAL NOT NULL)";
                comm.ExecuteNonQuery();
            }
        }
    }
}

Nota: aqui nós estamos adicionando também os 10 estabelecimentos “padrão” na tabela de estabelecimentos. Como já mencionei anteriormente, nós só estamos fazendo isso porque nós ainda não implementamos uma maneira de criarmos novos estabelecimentos na aplicação. Futuramente, esse código não será mais necessário.

Por fim, vamos alterar o nosso método “Carregar”, de forma que ele recupere os dados do banco (com comandos “SELECT“) e adicione as informações nas nossas listas:

public void Carregar()
{
    Estabelecimentos.Clear();
    Gastos.Clear();

    if (!System.IO.File.Exists(_caminhoBanco))
    {
        CriarBancoVazio();
    }

    using (var conn = new Mono.Data.Sqlite.SqliteConnection("Data Source=" + _caminhoBanco))
    {
        conn.Open();
        if (conn.State == System.Data.ConnectionState.Open)
        {
            using (var comm = new Mono.Data.Sqlite.SqliteCommand(conn))
            {
                comm.CommandText = "SELECT * FROM Estabelecimento";
                using (var reader = comm.ExecuteReader())
                {
                    using (var table = new System.Data.DataTable())
                    {
                        table.Load(reader);
                        foreach (System.Data.DataRow linha in table.Rows)
                        {
                            Estabelecimentos.Add(new Models.Estabelecimento()
                            {
                                Id = Convert.ToInt32(linha["Id"]),
                                Nome = linha["Nome"].ToString()
                            });
                        }
                    }
                }

                comm.CommandText = "SELECT * FROM Gasto";
                using (var reader = comm.ExecuteReader())
                {
                    using (var table = new System.Data.DataTable())
                    {
                        table.Load(reader);
                        foreach (System.Data.DataRow linha in table.Rows)
                        {
                            var estabelecimentoID = Convert.ToInt32(linha["EstabelecimentoId"]);
                            var estabelecimento = Estabelecimentos.FirstOrDefault(e => e.Id == estabelecimentoID);
                            Gastos.Add(new Models.Gasto()
                            {
                                Id = Convert.ToInt32(linha["Id"]),
                                Data = Convert.ToDateTime(linha["Data"]),
                                Estabelecimento = estabelecimento,
                                Valor = Convert.ToDecimal(linha["Valor"])
                            });
                        }
                    }
                }
            }
        }
    }
}

Observe que logo no início do método nós verificamos se o arquivo do banco de dados ainda não existe. Caso ele ainda não exista, nós criamos através de uma chamada para o método “CriarBancoVazio” (que vimos anteriormente).

E com isso nós teremos os dados sendo salvos no nosso banco SQLite. Fácil, não é mesmo? Pois continue lendo, porque tudo isso vai ficar ainda mais fácil!

SQLite no Xamarin Android com a biblioteca SQLite.Net

A segunda opção que temos para trabalharmos com o SQLite no Xamarin Android é a biblioteca SQLite.Net. Essa biblioteca implementa um “micro-ORM” bem enxuto para facilitar o código CRUD (create, update, delete) das nossas aplicações. Se você não sabe o que é um “micro-ORM“, dê uma lida no meu artigo de introdução ao Dapper, que é justamente o micro-ORM mais conhecido atualmente na plataforma .NET.

Para utilizarmos a biblioteca SQLite.Net no nosso projeto, temos que adicionar uma referência pelo NuGet:

Uma vez adicionada essa referência, a primeira coisa que temos que fazer é adicionarmos o mapeamento das nossas classes e propriedades com as nossas tabelas e campos do banco de dados. Esse tipo de mapeamento na biblioteca SQLite.Net é feito através de “data annotations“, que nada mais são que atributos nas classes e propriedades que basicamente informarão para a biblioteca quais são as tabelas e campos correspondentes.

Veja como fica o código das nossas classes “Estabelecimento” e “Gasto” com a adição das data annotations:

using SQLite.Net.Attributes;

namespace ControleDeGastos.Android.Models
{
    [Table("Estabelecimento")]
    public class Estabelecimento
    {
        [PrimaryKey, AutoIncrement]
        public int Id { get; set; }
        [NotNull]
        public string Nome { get; set; }
    }
}
using SQLite.Net.Attributes;

namespace ControleDeGastos.Android.Models
{
    [Table("Gasto")]
    public class Gasto
    {
        [PrimaryKey, AutoIncrement]
        public int Id { get; set; }
        [NotNull]
        public DateTime Data { get; set; }
        [Ignore]
        public Estabelecimento Estabelecimento { get; set; }
        [NotNull]
        public int EstabelecimentoId { get; set; }
        [NotNull]
        public decimal Valor { get; set; }
    }
}

Nota: perceba que nós tivemos que adicionar uma propriedade “EstabelecimentoID” na classe “Gasto”. Isso foi feito porque a biblioteca SQLite.Net não suporta relacionamentos nativamente. Existe uma extensão para ela (chamada SQLite-Net Extensions) que implementa essa funcionalidade, mas eu não tive tempo de testar ainda.

Agora veja só como fica simples o nosso código para salvarmos os gastos no banco de dados:

public void Salvar()
{
    using (var db = new SQLite.Net.SQLiteConnection(new SQLite.Net.Platform.XamarinAndroid.SQLitePlatformAndroid(), _caminhoBanco, storeDateTimeAsTicks: false))
    {
        db.Execute("DELETE FROM Gasto");
        foreach (var gasto in Gastos)
        {
            gasto.EstabelecimentoId = gasto.Estabelecimento.Id;
        }
        db.InsertAll(Gastos);
    }
}

Veja que nós só precisamos chamar o método “InsertAll” passando a nossa lista de gastos, aí a biblioteca cuidará do resto para gente. Como mencionei anteriormente, essa biblioteca não implementa nativamente o suporte para relacionamentos, então tive que fazer o mapeamento da propriedade “EstabelecimentoID” manualmente.

O código para criarmos o banco vazio também é muito simples. A criação das tabelas é feita através do método “CreateTable“, passando o tipo da entidade que deve ser levada em consideração:

private void CriarBancoVazio()
{
    using (var db = new SQLite.Net.SQLiteConnection(new SQLite.Net.Platform.XamarinAndroid.SQLitePlatformAndroid(), _caminhoBanco, storeDateTimeAsTicks: false))
    {
        db.CreateTable<Models.Estabelecimento>();

        for (int c = 1; c <= 10; c++)
        {
            db.Insert(new Models.Estabelecimento() { Nome = string.Format("Estabelecimento {0}", c) });
        }

        db.CreateTable<Models.Gasto>();
    }
}

Por fim, o carregamento dos dados também é super-descomplicado. Nós só temos que chamar o método “Table” passando o tipo desejado, aí a biblioteca retornará para gente uma lista preenchida com as entidades que estão cadastradas no banco de dados:

public void Carregar()
{
    if (!System.IO.File.Exists(_caminhoBanco))
    {
        CriarBancoVazio();
    }

    using (var db = new SQLite.Net.SQLiteConnection(new SQLite.Net.Platform.XamarinAndroid.SQLitePlatformAndroid(), _caminhoBanco, storeDateTimeAsTicks: false))
    {
        Estabelecimentos = db.Table<Models.Estabelecimento>().ToList();
        Gastos = db.Table<Models.Gasto>().ToList();
        foreach (var gasto in Gastos)
        {
            gasto.Estabelecimento = Estabelecimentos.FirstOrDefault(e => e.Id == gasto.EstabelecimentoId);
        }
    }
}

Aqui nós também tivemos que fazer o mapeamento inverso de “EstabelecimentoID” para uma instância de “Estabelecimento“. Tudo isso porque a biblioteca não suporta a questão de relacionamentos. Mas, de qualquer forma, a complexidade do código nem se compara com a versão em que utilizamos ADO.NET puro.

Acesse o código no GitHub

Normalmente eu disponibilizo os códigos de exemplo dos artigos e vídeos somente para as pessoas que se inscrevem na minha newsletter. Porém, como nessa série nós evoluiremos o projeto em cada artigo, achei mais interessante publicar o código no GitHub, onde todos poderão acompanhar a evolução do projeto quando novos artigos forem sendo publicados. Caso você se interesse, acesse o repositório desse projeto no meu GitHub.

Concluindo

No artigo de hoje nós aprendemos a armazenar informações nas nossas aplicações Android com Xamarin Native. Como mencionado no artigo, o armazenamento de dados pode ser implementado de três maneiras: nas preferências da aplicação, em arquivos ou em banco de dados local.

As preferências da aplicação são excelentes para guardarmos combinações de chave/valor. Já para dados mais complexos, nós temos que partir para o armazenamento em arquivos ou bancos de dados. No Xamarin nós podemos trabalhar com arquivos JSON muito facilmente através da biblioteca DataContractJsonSerializer. E, por fim, quando falamos de bancos de dados no Xamarin, a primeira opção que vem à mente é o SQLite, que tem suporte nativo tanto para Android quanto para iOS.

A implementação do SQLite no Xamarin Android pode ser feita tanto através de ADO.NET puro (com comandos manuais de INSERT, UPDATE e DELETE) quanto com um micro-ORM chamado SQLite.Net. No artigo você conferiu essas duas implementações e as diferenças entre elas.

E você, já precisou armazenar dados nas suas aplicações Xamarin? Qual sistemática você utilizou? Além dessas opções apresentadas nesse artigo, eu já ouvi falar também de outro banco de dados local bem conhecido, chamado “Realm“. Entretanto, eu não tive tempo ainda de analisa-lo. Quem sabe mais para frente eu escreva um outro artigo sobre ele. Fico aguardando os seus comentários logo abaixo!

Até a próxima!

André Lima

Newsletter do André Lima

* indicates required



Powered by MailChimp

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *