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
Our remote data samples in our samples browser and documentation give you lots of details on how to load remote OData services into the grid. But we didn’t stop there, additionally, you can also create your own custom versions of VirtualDataSource to virtualize access to other types of remote, or even local, data. In fact, recently, customers were asking us about whether it was possible to use our data grid with a SQLite database, without first loading all the data for a table into memory. This would be required if you wanted to provide some collection directly to the ItemsSource property on the grid, but there’s a better way if you extend VirtualDataSource. Lucky for you, though, I already did it.
If you build that project you’ll wind up with a SQLite specific version of our VirtualDataSource. This allows for linking to a table, or a joined set of tables, and then allowing for you to seamlessly page over it as if you were scrolling through a large, unbroken contiguous collection. Better yet, you can limit the amount of data pages the data source will keep in memory at one time, so you can put an upper bound on the memory usage in your mobile application.
SQLite Database Setup
Ok, so let’s put it into practice. Given you have a Xamarin.Forms project set up using the XamDataGrid, you first need to add a SQLite database to the Android app and the iOS app. For the Android App, this goes in the assets:
The Build Action for the database should be marked as AndroidAsset:
Given that, this logic, when placed in MainActivity.cs, right before Xamarin.Forms is initialized and before the main app is created, will make sure that the SQLite database is accessible to the application at 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);
}
}
}
For iOS, you should place the database file in the Resources for the application:
And make sure that the Build Action is set to BundleResource:
Given the database file being properly included, this logic, when placed in AppDelegate.cs, right before Xamarin.Forms is initialized and before the main app is created, would ensure that it is accessible to the iOS application at 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);
}
For both platforms, the file path to the SQLite database can now be passed into the Xamarin.Forms App when it is created:
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
To read data from a SQLite database, first you need a SQLite client that is compatible with a PCL (portable class library) and/or Xamarin.Android/Xamarin.iOS, so we’ll install the sqlite-net-pcl Nuget package.
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; }
}
}
This type maps to the tracks table in the Chinook SQLite sample database, which stores sample data about various tracks off popular music albums. We’ve indicated here, via attributes, various meta information about the table, such as the primary key, and maximum lengths of some of the string columns.
Now that data can be loaded from the tracks table, we are all set up to scroll over that table in the 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>
In the XAML, we’ve defined a XamDataGrid and configured some columns, just as if we were about to bind some in memory data to the grid. We could have skipped defining the columns and allowed them to auto generate, but there are sufficient number of columns on the tracks table that this would get pretty crowded.
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.
In the TableExpression we simply could have provided tracks, alternatively, but this example creates a join against the albums table in order to look up the album titles so that they can be populated in the AlbumTitle property.
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.