Ir para o conteúdo
Role perfeitamente por grandes tabelas SQLite no Xamarin.Forms com pouca sobrecarga de memória

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.

9min read

 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:

Para o aplicativo Android, isso vai nos ativos

O Build Action para o banco de dados deve ser marcado como AndroidAsset:

A Ação de Build do banco de dados deve ser marcada 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:

Para iOS, você deve colocar o arquivo de banco de dados nos Recursos do aplicativo

And make sure that the Build Action is set to BundleResource:

E verifique se a Ação de Build está definida como 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.

então vamos instalar o pacote Nuget sqlite-net-pcl.

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:

Aqui está como ficará

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.

Solicite uma demonstração