Saltar al contenido
Desplácese sin problemas por grandes tablas SQLite en Xamarin.Forms con poca sobrecarga de memoria

Desplácese sin problemas por grandes tablas SQLite en Xamarin.Forms con poca sobrecarga de memoria

Si ya ha trabajado con Infragistics' Xamarin.Forms/Xamarin.Android/Xamarin.iOS DataGrid (XamDataGrid), habrá descubierto que conoce algunos trucos muy interesantes.

9min read

 Puede vincularlo a un servicio OData remoto y desplazarse por las filas, y usará la velocidad de su movimiento para predecir cuándo necesita recuperar datos y cargarlos sin problemas antes de que llegue allí. Si aún no has visto este truco, definitivamente echa un vistazo a nuestro navegador de muestras para Ultimate UI for Xamarin, que lo muestra bien.

Running the Sample

Puede obtener la muestra que crearemos en este artículo aquí. Una vez que abra el ejemplo, deberá asegurarse de que tiene nuestros paquetes Nuget de prueba o RTM en el repositorio local y restaurar los paquetes Nuget.

Virtualización del acceso a otras fuentes de datos

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:

En el caso de la aplicación de Android, esto va en los activos

The Build Action for the database should be marked as AndroidAsset:

La acción de compilación de la base de datos debe marcarse como 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:

Para iOS, debe colocar el archivo de base de datos en los recursos de la aplicación

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

Y asegúrese de que la acción de compilación esté establecida en 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));

A continuación, la aplicación se asegurará de que la ruta esté disponible para la página que utilizaremos:

public App(string dbPath)
{
    InitializeComponent();
    MainPage = new SQLDemo.MainPage(dbPath);
}

Desplazamiento virtual en vivo a través de tablas 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.

por lo tanto, instalaremos el paquete Nuget sqlite-net-pcl.

La biblioteca SQLite.NET incluye una herramienta ORM ligera que utilizará para hidratar los datos que se leen en los tipos POCO, por lo que primero debemos crear un tipo POCO para la tabla que nos interesa:

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.

En primer lugar, podemos diseñar la cuadrícula en 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.

Bien, entonces, ¿cómo vinculamos la cuadrícula con la tabla SQLite? Primero tenemos que crear una conexión para hablar con la base de datos SQLite:

_connection = new SQLiteAsyncConnection(dbPath);

Donde dbPath es la ruta de acceso del archivo a la base de datos SQLite que pasamos anteriormente. A continuación, solo tenemos que crear un SQLiteVirtualDataSource, configurarlo y asignarlo a la cuadrícula:

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;

Aquí:

  • Proporcione la conexión que creamos a la fuente de datos virtual.
  • Proporcione una expresión de tabla al origen de datos virtual para indicar de qué tabla extraer datos.
  • Indique el tipo de POCO que creamos para hidratar las filas de datos.

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.

¡Y eso es todo! Si ejecuta la aplicación, verá que puede desplazarse por la tabla como si fuera solo un conjunto largo y contiguo de registros. Sin embargo, en realidad, solo una fracción de la tabla está en la memoria del dispositivo a la vez. Es posible que tenga problemas para desplazarse lo suficientemente rápido como para ver un escenario en el que llegue a algunos registros antes de que se hayan cargado, porque la cuadrícula en realidad los carga de manera predictiva en segundo plano. Así es como se verá:

Así es como se verá

Sin embargo, puede ver que la cuadrícula se pone al día si cambia el tipo de la cuadrícula tocando los encabezados de las columnas. Esto hace que se invaliden los datos actuales del lado del cliente y que se obtengan los nuevos datos, ordenados según lo solicitado, pero, de nuevo, solo en la cantidad necesaria.

Adición de algunos filtros

Ok, tomemos eso y hagamos las cosas un poco más elegantes, ¿de acuerdo? En primer lugar, agregue esto a la cuadrícula del XAML de la página:

<StackLayout Orientation="Horizontal" Grid.Row="1">
    <Label Text="Filter" />
    <Entry TextChanged="Entry_TextChanged" WidthRequest="300" />
</StackLayout>

Ese marcado ha agregado un campo de entrada para que podamos recopilar un valor de filtro por el cual filtrar la tabla que estamos mostrando. Un evento se activa cada vez que el texto de la entrada ha cambiado. Así que agreguemos el controlador para eso al código subyacente:

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));
            }));
    }
}

Este código borrará los filtros de cuadrícula si el campo de entrada se queda en blanco, pero de lo contrario, creará un filtro para ver si Name o AlbumTitle o Composer coinciden con la cadena proporcionada y se asegurará de que el filtro se use en las consultas pasadas a SQLite.

Este es el aspecto que tiene ahora el ejemplo:

Como puede ver, cada vez que escriba una letra, la cuadrícula local deberá actualizar su contenido con el nuevo contenido filtrado, por el que luego puede desplazarse en su totalidad.

Puedes obtener más información consultando nuestras lecciones y videos "Escribe rápido" y "Corre rápido". También querrá asegurarse de descargar una versión de prueba gratuita de Infragistics Ultimate UI for Xamarin.

Graham Murray es arquitecto de software y autor. Crea componentes de interfaz de usuario multiplataforma de alto rendimiento para Infragistics, que abarcan equipos de escritorio, web y dispositivos móviles. Síguelo en Twitter en @the_graham.

Solicitar una demostración