コンテンツへスキップ
Xamarin.Forms の大きな SQLite テーブルをシームレスにスクロールし、メモリ オーバーヘッドを抑える

Xamarin.Forms の大きな SQLite テーブルをシームレスにスクロールし、メモリ オーバーヘッドを抑える

InfragisticsのXamarin.Forms/Xamarin.Android/Xamarin.iOS DataGrid(XamDataGrid)をすでに使用している場合は、非常に優れたトリックを知っていることに気付くでしょう。

9min read

 リモート OData サービスにバインドして行をスクロールすると、移動速度を使用してデータをフェッチする必要があるタイミングを予測し、そこに到達する前にシームレスに読み込むことができます。このトリックをまだ見ていない場合は、間違いなくサンプルブラウザをチェックしてくださいUltimate UI for Xamarin、これをよく示しています。

Running the Sample

この記事で作成するサンプルは、こちらから入手できます。サンプルを開いたら、ローカル リポジトリに試用版または RTM Nuget パッケージがあることを確認し、Nuget パッケージを復元する必要があります。

他のデータソースへのアクセスの仮想化

サンプルブラウザとドキュメント内のリモートデータサンプルには、リモートODataサービスをグリッドにロードする方法について多くの詳細が記載されています。しかしそれだけにとどまりません。さらに、他の種類のリモートやローカルデータへのアクセスを仮想化するために、自分だけのカスタムVirtualDataSourceバージョンを作成することも可能です。実際、最近ではお客様から、テーブルの全データをメモリに読み込まずにSQLiteデータベースでデータグリッドを使えるかどうか尋ねられました。グリッド上のItemsSourceプロパティに直接コレクションを提供したい場合は必要ですが、拡張すればより良い方法がありますVirtualDataSource。でも運がいいな、もうやったよ。

そのプロジェクトを作れば、私たちのSQLite専用バージョンができVirtualDataSource。これにより、テーブルや連結されたテーブルのセットにリンクし、まるで大きく途切れない連続したコレクションをスクロールするかのようにシームレスにページをめくることができます。さらに良いのは、データソースが一度にメモリに保持するデータページ数を制限することで、モバイルアプリケーションでのメモリ使用量に上限を設けられることです。

SQLite Database Setup

では、実践に移しましょう。Xamarin.FormsプロジェクトがXamDataGridを使って構築されているなら、まずAndroidアプリとiOSアプリにSQLiteデータベースを追加する必要があります。Androidアプリについては、以下のアセットに入ります:

Androidアプリの場合、これはアセットに含まれます

データベースのBuild Actionは次のようにマークしてくださいAndroidAsset:

データベースのビルド アクションは、AndroidAsset としてマークする必要があります

このロジックをXamarin.Formsの初期化直前、メインアプリが作成される直前にMainActivity.cs置くと、実行時にSQLiteデータベースがアプリケーションにアクセスできるようにします。

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

iOSの場合は、データベースファイルをアプリケーションのResourcesに入れてください:

iOS の場合は、アプリケーションのリソースにデータベース ファイルを配置する必要があります

そして、Build ActionBundleResourceに設定されていることを確認してください:

また、ビルド アクションが BundleResource に設定されていることを確認します

データベースファイルが正しく含まれていると、このロジックはXamarin.Formsの初期化直前でメインアプリが作成される前にAppDelegate.csに入れることで、実行時にiOSアプリケーションにアクセス可能であることを保証します。

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

両プラットフォームにおいて、SQLiteデータベースへのファイルパスはXamarin.FormsApp作成時に渡すことができます。

LoadApplication(new App(path));

その後、アプリは、使用するページへのパスが利用可能であることを確認します。

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

SQLiteテーブルをライブ仮想スクロール

SQLiteデータベースからデータを読み取るには、まずPCL(ポータブルクラスライブラリ)やXamarin.Android/Xamarin.iOSに対応したSQLiteクライアントが必要で、sqlite-net-pcl Nugetパッケージをインストールします。

そのため、sqlite-net-pcl Nugetパッケージをインストールします。

SQLite.NET ライブラリには、POCOタイプに読み取られるデータをハイドレートするために使用する軽量のORMツールが含まれているため、最初に関心のあるテーブルのPOCOタイプを作成する必要があります。

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

このタイプは、人気音楽アルバムの様々なトラックのサンプルデータを保存するChinook SQLiteサンプルデータベースのtracksテーブルにマッピングされます。ここでは属性を通じて、主キーや一部の文字列列の最大長さなど、テーブルに関する様々なメタ情報を示しています。

今やtracksテーブルからデータを読み込めるようになり、XamDataGridでそのテーブルをスクロールする設定が整いました。

まず、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>

XAMLでは、メモリ内のデータをグリッドに割り当てるかのように、いくつかの列を定義XamDataGrid設定しています。列の定義を飛ばして自動生成させることもできたはずですが、tracksテーブルには列が十分にあるため、かなり混雑してしまいます。

では、SQLiteテーブルに対してグリッドをどのようにバインドしますか?まず、SQLiteデータベースと通信するための接続を作成する必要があります。

_connection = new SQLiteAsyncConnection(dbPath);

ここで、dbPath は、前に渡した SQLite データベースへのファイルパスです。次に、SQLiteVirtualDataSource を作成し、構成してグリッドに割り当てるだけです。

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;

ここでは、

  • 作成した接続を仮想データ ソースに提供します。
  • 仮想データソースにテーブル式を提供して、データをプルするテーブルを示します。
  • データ行をハイドレートするために作成した POCO タイプを指定します。

TableExpressionwe could had providedtracksでは、この例ではアルバムタイトルを調べてAlbumTitleプロパティに入力できるように、アルバムテーブルに対してジョインを作成します。

以上です!アプリを実行すると、テーブルを 1 つの長い連続したレコード セットであるかのようにスクロールできることがわかります。ただし、実際には、テーブルのごく一部が一度にデバイスのメモリに存在します。ロードされる前に一部のレコードに到達するシナリオを見るのに十分な速さでスクロールするのが難しい場合があります。これは、グリッドが実際に予測的にレコードをロードするためです。次のように表示されます。

これは、どのように見えるかです

ただし、列ヘッダーをタップしてグリッドの種類を変更すると、グリッドが追いついていることがわかります。これにより、現在のクライアント側のデータは無効になり、要求どおりにソートされた新しいデータがフェッチされますが、ここでも必要な分だけフェッチされます。

フィルタリングの追加

では、それを受けて、もう少しおしゃれにしましょう。まず、ページの XAML のグリッドに以下を追加します。

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

そのマークアップにより、表示しているテーブルをフィルタリングするためのフィルター値を収集できるように、入力フィールドが追加されました。エントリのテキストが変更されるたびにイベントが発生します。それでは、そのためのハンドラを分離コードに追加しましょう。

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

このコードは、入力フィールドが空白になった場合はグリッド フィルターをクリアしますが、それ以外の場合は、Name、AlbumTitle、または Composer が指定された文字列と一致するかどうかを確認し、そのフィルターが SQLite に渡されるクエリで使用されることを確認するフィルターを作成します。

サンプルは次のようになります。

ご覧のとおり、文字を入力するたびに、ローカル グリッドは新しいフィルター処理されたコンテンツでそのコンテンツを更新する必要があり、その後、全体をスクロールできます。

詳細については、「Write Fast」と「Run Fast」のレッスンとビデオをご覧ください。また、Infragistics Ultimate UI for Xamarin無料トライアルも必ずダウンロードすることをお勧めします。

Graham Murrayは、ソフトウェア アーキテクトであり、著者です。彼は、デスクトップ、Web、モバイルにまたがる Infragistics 向けの高性能クロスプラットフォーム UI コンポーネントを構築しています。Twitterで@the_grahamをフォローしてください。

デモを予約