Role perfeitamente por grandes tabelas SQLite no Xamarin.Forms com pouca sobrecarga de memória
Se você já trabalhou com Infragistics' Xamarin.Forms/Xamarin.Android/Xamarin.iOS DataGrid (XamDataGrid), você terá descoberto que ele conhece alguns truques muito legais.
Você pode associá-lo a um serviço OData remoto e rolar pelas linhas, e ele usará a velocidade do seu movimento para prever quando ele precisa buscar dados e carregá-los perfeitamente antes de você chegar lá. Se você ainda não viu esse truque, confira definitivamente nosso navegador de amostras para Ultimate UI for Xamarin, que mostra isso bem.
Running the Sample
Você pode obter o exemplo que criaremos neste artigo aqui. Depois de abrir o exemplo, você precisará verificar se tem nossos pacotes Nuget de avaliação ou RTM em seu repositório local e restaurar os pacotes Nuget.
Virtualizando o acesso a outras fontes de dados
Nossos dados remotos no navegador de exemplos e na documentação fornecem muitos detalhes sobre como carregar serviços remotos OData na rede. Mas não paramos por aí; além disso, você também pode criar suas próprias versõesVirtualDataSource personalizadas para virtualizar o acesso a outros tipos de dados remotos ou até locais. Na verdade, recentemente, clientes nos perguntavam se era possível usar nossa grade de dados com um banco de dados SQLite, sem antes carregar todos os dados de uma tabela na memória. Isso seria necessário se você quiser fornecer alguma coleção diretamente para a propriedade ItemsSource na grade, mas há uma maneira melhor se você estenderVirtualDataSource. Mas sorte sua é que eu já fiz isso.
Se você construir esse projeto, vai acabar com uma versão específica do SQLite do nossoVirtualDataSource. Isso permite linkar para uma tabela, ou um conjunto conjunto de tabelas unidas, e então permitir que você folheie tudo de forma fluida, como se estivesse rolando por uma grande coleção contínua e contínua. Melhor ainda, você pode limitar a quantidade de páginas de dados que a fonte de dados manterá na memória de uma vez, assim pode impor um limite superior para o uso de memória no seu aplicativo móvel.
SQLite Database Setup
Ok, então vamos colocar isso em prática. Considerando que você tem um projeto Xamarin.Forms configurado usando issoXamDataGrid, primeiro precisa adicionar um banco de dados SQLite ao app Android e ao app iOS. Para o aplicativo Android, isso vai nos assets:
OBuild Action para o banco de dados deve ser marcado comoAndroidAsset:
Dado isso, essa lógica, quando inseridaMainActivity.cs logo antes do Xamarin.Forms ser inicializado e antes da criação do aplicativo principal, garantirá que o banco de dados SQLite esteja acessível para a aplicação em tempo de execução:
string targetPath =
System.Environment.GetFolderPath(
System.Environment.SpecialFolder.Personal
);
var path = Path.Combine(
targetPath, "chinook.db");
if (!File.Exists(path))
{
using (Stream input =
Assets.Open("chinook.db"))
{
using (var fs = new FileStream(
path,
FileMode.Create))
{
input.CopyTo(fs);
}
}
}
Para iOS, você deve colocar o arquivo de banco de dados naResources aplicação da aplicação:
E certifique-se de que oBuild Action esteja definido paraBundleResource:
Dado que o arquivo de banco de dados está devidamente incluído, essa lógica, quando inseridaAppDelegate.cs logo antes do Xamarin.Forms ser inicializado e antes da criação do aplicativo principal, garantiria que ele fosse acessível ao aplicativo iOS em tempo de execução:
var targetPath = Environment.GetFolderPath(
Environment.SpecialFolder.Personal);
targetPath = Path.Combine(targetPath, "..", "Library");
var path = Path.Combine(targetPath, "chinook.db");
if (!File.Exists(path))
{
var bundlePath = NSBundle.MainBundle.PathForResource(
"chinook",
"db"
);
File.Copy(bundlePath, path);
}
Para ambas as plataformas, o caminho do arquivo para o banco de dados SQLite agora pode ser passado para o Xamarin.FormsApp quando for criado:
LoadApplication(new App(path));
O aplicativo apenas garantirá que o caminho esteja disponível para a página que usaremos:
public App(string dbPath)
{
InitializeComponent();
MainPage = new SQLDemo.MainPage(dbPath);
}
Rolagem virtual ao vivo por tabelas SQLite
Para ler dados de um banco de dados SQLite, primeiro você precisa de um cliente SQLite compatível com uma PCL (biblioteca de classes portátil) e/ou Xamarin.Android/Xamarin.iOS, então vamos instalar osqlite-net-pcl pacote Nuget.
A biblioteca SQLite.NET inclui uma ferramenta ORM leve que será usada para hidratar os dados que estão sendo lidos em tipos POCO, portanto, primeiro precisamos criar um tipo POCO para a tabela em que estamos interessados:
using SQLite;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SQLDemo.Data
{
[Table("tracks")]
public class Track
{
[PrimaryKey, AutoIncrement]
public int TrackId { get; set; }
[MaxLength(200)]
public string Name { get; set; }
public int AlbumId { get; set; }
[Column("Title")]
public string AlbumTitle { get; set; }
public int MediaTypeId { get; set; }
public int GenreId { get; set; }
[MaxLength(220)]
public string Composer { get; set; }
public int Milliseconds { get; set; }
public int Bytes { get; set; }
public decimal UnitPrice { get; set; }
}
}
Esse tipo mapeia para atracks tabela no banco de dados de samples do Chinook SQLite, que armazena dados de amostras sobre várias faixas de álbuns de música popular. Indicamos aqui, por meio de atributos, várias metainformações sobre a tabela, como a chave primária, e os comprimentos máximos de algumas colunas de string.
Agora que os dados podem ser carregados datracks tabela, todos estamos prontos para rolar sobre essa tabela na tabelaXamDataGrid.
Primeiro, podemos definir a grade em XAML:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:SQLDemo"
x:Class="SQLDemo.MainPage"
xmlns:igGrid="clr-namespace:Infragistics.XamarinForms.Controls.Grids;assembly=Infragistics.XF.DataGrid">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<igGrid:XamDataGrid x:Name="grid" RowHeight="90"
SelectionMode="MultipleRow"
HeaderClickAction="SortByMultipleColumnsTriState"
AutoGenerateColumns="False">
<igGrid:XamDataGrid.Columns>
<igGrid:TextColumn PropertyPath="Name"
LineBreakMode="WordWrap"
Width="1*"
/>
<igGrid:TextColumn PropertyPath="Composer"
LineBreakMode="Ellipsis"
Width="1.25*"/>
<igGrid:TextColumn PropertyPath="AlbumTitle"
HeaderText="Album Title"
LineBreakMode="WordWrap"
Width="1*"/>
<igGrid:NumericColumn PropertyPath="UnitPrice"
HeaderText="Unit Price"
MinFractionDigits="2"
Width="1*"/>
</igGrid:XamDataGrid.Columns>
</igGrid:XamDataGrid>
</Grid>
</ContentPage>
No XAML, definimos eXamDataGrid configuramos algumas colunas, como se estivéssemos prestes a vincular alguns dados em memória à grade. Poderíamos ter pulado a definição das colunas e permitido que elas se gerassem automaticamente, mas há um número suficiente de colunas natracks mesa para que isso ficasse bem lotado.
Ok, então, como vinculamos a grade à tabela SQLite? Primeiro precisamos criar uma conexão para conversar com o banco de dados SQLite:
_connection = new SQLiteAsyncConnection(dbPath);
Onde dbPath é o caminho do arquivo para o banco de dados SQLite que passamos anteriormente. Então, só precisamos criar um SQLiteVirtualDataSource, configurá-lo e atribuí-lo à grade:
var dataSource = new SQLiteVirtualDataSource();
dataSource.Connection = _connection;
dataSource.TableExpression =
"tracks left outer join albums on tracks.AlbumId = albums.AlbumId";
dataSource.ProjectionType = typeof(Track);
grid.ItemsSource = dataSource;
Aqui nós:
- Forneça a conexão que criamos com a fonte de dados virtual.
- Forneça uma expressão de tabela para a fonte de dados virtual, para indicar de qual tabela extrair dados.
- Indique o tipo de POCO que criamos para hidratar as linhas de dados.
NoTableExpression nós simplesmente poderíamos ter fornecidotracks, alternativamente, mas este exemplo cria uma junção contra a tabela de álbuns para consultar os títulos dos álbuns e que possam ser preenchidos naAlbumTitle propriedade.
E é isso! Se você executar o aplicativo, verá que pode rolar pela tabela como se fosse apenas um longo conjunto contíguo de registros. Na realidade, porém, apenas uma fração da tabela está na memória do dispositivo de uma só vez. Você pode ter problemas para rolar rápido o suficiente para ver um cenário em que você chega a alguns registros antes que eles sejam carregados, porque a grade realmente os carrega de forma preditiva sob as cobertas. Aqui está o que vai parecer:

Você pode ver a grade se atualizando, no entanto, se alterar o tipo da grade tocando nos cabeçalhos das colunas. Isso faz com que os dados atuais do lado do cliente sejam invalidados e novos dados, classificados conforme solicitado, sejam buscados, mas, novamente, apenas o necessário.
Adicionando alguma filtragem
Ok, vamos pegar isso e tornar as coisas um pouco mais sofisticadas, certo? Primeiro, adicione isso à grade no XAML da página:
<StackLayout Orientation="Horizontal" Grid.Row="1">
<Label Text="Filter" />
<Entry TextChanged="Entry_TextChanged" WidthRequest="300" />
</StackLayout>
Essa marcação adicionou um campo de entrada para que possamos coletar um valor de filtro para filtrar a tabela que estamos exibindo. Um evento é acionado sempre que o texto da entrada é alterado. Então, vamos adicionar o manipulador para isso ao código por trás:
private void Entry_TextChanged(object sender, TextChangedEventArgs e)
{
if (String.IsNullOrEmpty(e.NewTextValue))
{
grid.FilterExpressions.Clear();
}
else
{
grid.FilterExpressions.Clear();
grid.FilterExpressions.Add(FilterFactory.Build(
(f) =>
{
return f.Property("Name").Contains(e.NewTextValue)
.Or(f.Property("AlbumTitle").Contains(e.NewTextValue))
.Or(f.Property("Composer").Contains(e.NewTextValue));
}));
}
}
Esse código limpará os filtros de grades se o campo de entrada ficar em branco, mas, caso contrário, criará um filtro para ver se Name, AlbumTitle ou Composer correspondem à cadeia de caracteres fornecida e garantirá que o filtro seja usado nas consultas passadas para SQLite.
Veja como é o exemplo agora:

Como você pode ver, toda vez que você digitar uma letra, a grade local precisará atualizar seu conteúdo com o novo conteúdo filtrado, que você pode percorrer em sua totalidade.
Você pode aprender mais conferindo nossas lições e vídeos "Escreva rápido" e "Corra rápido". Você também vai querer ter certeza de baixar uma avaliação gratuita do Infragistics Ultimate UI for Xamarin.
Graham Murray é arquiteto de software e autor. Ele cria componentes de interface do usuário multiplataforma de alto desempenho para Infragistics, abrangendo desktop, web e dispositivos móveis. Siga-o no Twitter em @the_graham.