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 exemplos de dados remotos em nosso navegador de exemplos e documentação fornecem muitos detalhes sobre como carregar serviços OData remotos na grade. Mas não paramos por aí, além disso, você também pode criar suas próprias versões personalizadas para VirtualDataSource
virtualizar o acesso a outros tipos de dados remotos ou mesmo locais. Na verdade, recentemente, os clientes estavam nos perguntando se era possível usar nossa grade de dados com um banco de dados SQLite, sem primeiro carregar todos os dados de uma tabela na memória. Isso seria necessário se você quisesse fornecer alguma coleção diretamente para a propriedade ItemsSource na grade, mas há uma maneira melhor se você estender VirtualDataSource
. Para sua sorte, porém, eu já fiz isso.
Se você construir esse projeto, acabará com uma versão específica do SQLite do nosso VirtualDataSource
. Isso permite vincular a uma tabela ou a um conjunto de tabelas unidas e, em seguida, permitir que você percorra perfeitamente como se estivesse rolando por uma coleção contígua grande e ininterrupta. Melhor ainda, você pode limitar a quantidade de páginas de dados que a fonte de dados manterá na memória de uma só vez, para que você possa colocar um limite superior no uso de memória em seu aplicativo móvel.
SQLite Database Setup
Ok, então vamos colocar em prática. Considerando que você tem um projeto Xamarin.Forms configurado usando o XamDataGrid
, primeiro você precisa adicionar um banco de dados SQLite ao aplicativo Android e ao aplicativo iOS. Para o aplicativo Android, isso vai nos ativos:
O Build Action
para o banco de dados deve ser marcado como AndroidAsset
:
Considerando que, essa lógica, quando colocada MainActivity.cs
, logo antes da inicialização do Xamarin.Forms e antes da criação do aplicativo principal, garantirá que o banco de dados SQLite esteja acessível ao aplicativo em runtime:
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 no Resources
para o aplicativo:
And make sure that the Build Action
is set to BundleResource
:
Considerando que o arquivo de banco de dados está sendo incluído corretamente, essa lógica, quando colocada AppDelegate.cs
, logo antes de o Xamarin.Forms ser inicializado e antes da criação do aplicativo principal, garantiria que ele estivesse acessível ao aplicativo iOS em runtime:
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.Forms App
quando ele é 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, portanto, instalaremos o sqlite-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 é mapeado para a tracks
tabela no banco de dados de exemplo Chinook SQLite, que armazena dados de exemplo sobre várias faixas de álbuns de música populares. 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 das colunas de string.
Agora que os dados podem ser carregados da tracks
tabela, estamos todos configurados para rolar sobre essa tabela no XamDataGrid
.
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 e XamDataGrid
configuramos algumas colunas, como se estivéssemos prestes a associar alguns dados da memória à grade. Poderíamos ter pulado a definição das colunas e permitido que elas fossem geradas automaticamente, mas há um número suficiente de colunas na tracks
tabela para que isso ficasse bastante 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.
TableExpression
No que simplesmente poderíamos ter fornecido tracks
, alternativamente, mas este exemplo cria uma junção na tabela de álbuns para pesquisar os títulos dos álbuns para que eles possam ser preenchidos AlbumTitle
na 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.