{"id":707,"date":"2017-07-10T09:00:00","date_gmt":"2017-07-10T09:00:00","guid":{"rendered":"https:\/\/staging.infragistics.com\/blogs\/?p=707"},"modified":"2025-02-26T12:21:10","modified_gmt":"2025-02-26T12:21:10","slug":"scrolling-sqlite-tables-xamarin-forms","status":"publish","type":"post","link":"https:\/\/www.infragistics.com\/blogs\/scrolling-sqlite-tables-xamarin-forms","title":{"rendered":"Scroll Seamlessly Through Large SQLite Tables in Xamarin.Forms with Low Memory Overhead"},"content":{"rendered":"\n<p><a href=\"https:\/\/youtu.be\/MQgu2MFN9L8\" target=\"_blank\" rel=\"noopener noreferrer\"><img decoding=\"async\" width=\"200\" class=\"align-right\" style=\"float: right;\" src=\"https:\/\/users.infragistics.com\/gmurray\/Blogs\/Images\/SQlite\/SQLScroll.gif\" alt=\" \"><\/a>You can bind it to a remote OData service and scroll through the rows, and it will use the velocity of your movement to predict when it needs to fetch data and seamlessly load it before you get there. If you haven\u2019t seen this trick yet, definitely check out our samples browser for <a href=\"\/products\/xamarin\" target=\"_blank\" rel=\"noopener noreferrer\">Ultimate UI for Xamarin<\/a>, which shows this off well.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"running-the-sample\">Running the Sample<\/h2>\n\n\n\n<p>You can get the sample we&#8217;ll be building in this article <a href=\"https:\/\/github.com\/Infragistics\/XamarinDemoApps\/tree\/master\/SqlLiteVirtualDataSource\" target=\"_blank\" rel=\"noopener noreferrer\">here<\/a>. Once you open the sample, you&#8217;ll need to make sure you have our Trial or RTM Nuget packages in your local repository and <a href=\"\/help\/xamarin\/infragistics-nuget-feed\">restore the Nuget packages<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"virtualizing-access-to-other-data-sources\">Virtualizing Access to Other Data Sources<\/h2>\n\n\n\n<p>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&#8217;t stop there, additionally, you can also create your own custom versions of <code>VirtualDataSource<\/code> 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\u2019s a better way if you extend <code>VirtualDataSource<\/code>. Lucky for you, though, <a href=\"https:\/\/github.com\/Infragistics\/DataSource-Reference-Implementations\/tree\/master\/DataSource.DataProviders.SQLite\" target=\"_blank\" rel=\"noopener noreferrer\">I already did it<\/a>.<\/p>\n\n\n\n<p>If you build that project you\u2019ll wind up with a SQLite specific version of our <code>VirtualDataSource<\/code>. 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.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"sqlite-database-setup\">SQLite Database Setup<\/h2>\n\n\n\n<p>Ok, so let\u2019s put it into practice. Given you have a Xamarin.Forms project set up using the <code>XamDataGrid<\/code>, 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:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/users.infragistics.com\/gmurray\/Blogs\/Images\/SQlite\/DBFile.PNG\" alt=\" For the Android App, this goes in the assets\" title=\"For the Android App, this goes in the assets\"\/><\/figure>\n\n\n\n<p>The <code>Build Action<\/code> for the database should be marked as <code>AndroidAsset<\/code>:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/users.infragistics.com\/gmurray\/Blogs\/Images\/SQlite\/AndroidAsset.PNG\" alt=\" The Build Action for the database should be marked as AndroidAsset\" title=\"The Build Action for the database should be marked as AndroidAsset\"\/><\/figure>\n\n\n\n<p>Given that, this logic, when placed in <code>MainActivity.cs<\/code>, 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:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW hljs\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">string targetPath = \n    System.Environment.GetFolderPath(\n        System.Environment.SpecialFolder.Personal\n    );\nvar path = Path.Combine(\n    targetPath, \"chinook.db\");\n\nif (!File.Exists(path))\n{\n    using (Stream input = \n        Assets.Open(\"chinook.db\"))\n    {\n        using (var fs = new FileStream(\n            path, \n            FileMode.Create))\n        {\n            input.CopyTo(fs);\n        }\n    }\n}<\/pre>\n\n\n\n<p>For iOS, you should place the database file in the <code>Resources<\/code> for the application:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/users.infragistics.com\/gmurray\/Blogs\/Images\/SQlite\/iOSResources.PNG\" alt=\" For iOS, you should place the database file in the Resources for the application\" title=\"For iOS, you should place the database file in the Resources for the application\"\/><\/figure>\n\n\n\n<p>And make sure that the <code>Build Action<\/code> is set to <code>BundleResource<\/code>:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/users.infragistics.com\/gmurray\/Blogs\/Images\/SQlite\/BundleResource.PNG\" alt=\" And make sure that the Build Action is set to BundleResource\" title=\"And make sure that the Build Action is set to BundleResource\"\/><\/figure>\n\n\n\n<p>Given the database file being properly included, this logic, when placed in <code>AppDelegate.cs<\/code>, 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:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW hljs\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">var targetPath = Environment.GetFolderPath(\n    Environment.SpecialFolder.Personal);\ntargetPath = Path.Combine(targetPath, \"..\", \"Library\");\n\nvar path = Path.Combine(targetPath, \"chinook.db\");\nif (!File.Exists(path))\n{\n    var bundlePath = NSBundle.MainBundle.PathForResource(\n        \"chinook\", \n        \"db\"\n    );\n    File.Copy(bundlePath, path);\n} <\/pre>\n\n\n\n<p>For both platforms, the file path to the SQLite database can now be passed into the Xamarin.Forms <code>App<\/code> when it is created:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW hljs\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">LoadApplication(new App(path));<\/pre>\n\n\n\n<p>The app will then just make sure that path is available to the page we will be using:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW hljs\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">public App(string dbPath)\n{\n    InitializeComponent();\n    MainPage = new SQLDemo.MainPage(dbPath);\n}<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"live-virtual-scrolling-through-sqlite-tables\">Live Virtual Scrolling Through SQLite Tables<\/h2>\n\n\n\n<p>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&#8217;ll install the <code>sqlite-net-pcl<\/code> <a href=\"https:\/\/www.nuget.org\/packages\/sqlite-net-pcl\" target=\"_blank\" rel=\"noopener noreferrer\">Nuget package<\/a>.<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/users.infragistics.com\/gmurray\/Blogs\/Images\/SQlite\/sqlitenetpcl.PNG\" alt=\" so we'll install the sqlite-net-pcl Nuget package.\" title=\"so we'll install the sqlite-net-pcl Nuget package.\"\/><\/figure>\n\n\n\n<p>The SQLite.NET library includes a lightweight <a href=\"https:\/\/en.wikipedia.org\/wiki\/Object-relational_mapping\" target=\"_blank\" rel=\"noopener noreferrer\">ORM tool<\/a> that it will use to hydrate data being read into <a href=\"https:\/\/en.wikipedia.org\/wiki\/Plain_old_CLR_object\" target=\"_blank\" rel=\"noopener noreferrer\">POCO<\/a> types, so we first need to create a POCO type for the table we are interested in:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW hljs\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">using SQLite;\nusing System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace SQLDemo.Data\n{\n    [Table(\"tracks\")]\n    public class Track\n    {\n        [PrimaryKey, AutoIncrement]\n        public int TrackId { get; set; }\n\n        [MaxLength(200)]\n        public string Name { get; set; }\n\n        public int AlbumId { get; set; }\n\n        [Column(\"Title\")]\n        public string AlbumTitle { get; set; }\n\n        public int MediaTypeId { get; set; }\n\n        public int GenreId { get; set; }\n\n        [MaxLength(220)]\n        public string Composer { get; set; }\n\n        public int Milliseconds { get; set; }\n\n        public int Bytes { get; set; }\n\n        public decimal UnitPrice { get; set; }\n    }\n}<\/pre>\n\n\n\n<p>This type maps to the <code>tracks<\/code> table in the Chinook SQLite sample database, which stores sample data about various tracks off popular music albums. We&#8217;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.<\/p>\n\n\n\n<p>Now that data can be loaded from the <code>tracks<\/code> table, we are all set up to scroll over that table in the <code>XamDataGrid<\/code>.<\/p>\n\n\n\n<p>First, we can lay out the grid in XAML:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW hljs\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">&lt;?xml version=\"1.0\" encoding=\"utf-8\" ?>\n&lt;ContentPage xmlns=\"http:\/\/xamarin.com\/schemas\/2014\/forms\"\n             xmlns:x=\"http:\/\/schemas.microsoft.com\/winfx\/2009\/xaml\"\n             xmlns:local=\"clr-namespace:SQLDemo\"\n             x:Class=\"SQLDemo.MainPage\"\n             xmlns:igGrid=\"clr-namespace:Infragistics.XamarinForms.Controls.Grids;assembly=Infragistics.XF.DataGrid\">\n\n    &lt;Grid>\n        &lt;Grid.RowDefinitions>\n            &lt;RowDefinition \/>\n            &lt;RowDefinition Height=\"Auto\" \/>\n        &lt;\/Grid.RowDefinitions>\n\n        &lt;igGrid:XamDataGrid x:Name=\"grid\" RowHeight=\"90\"\n                        SelectionMode=\"MultipleRow\"\n                        HeaderClickAction=\"SortByMultipleColumnsTriState\"\n                        AutoGenerateColumns=\"False\">\n            &lt;igGrid:XamDataGrid.Columns>\n                &lt;igGrid:TextColumn PropertyPath=\"Name\" \n                                LineBreakMode=\"WordWrap\" \n                                Width=\"1*\"\n                                \/>\n                &lt;igGrid:TextColumn PropertyPath=\"Composer\" \n                                LineBreakMode=\"Ellipsis\"\n                                Width=\"1.25*\"\/>\n                &lt;igGrid:TextColumn PropertyPath=\"AlbumTitle\" \n                        HeaderText=\"Album Title\" \n                        LineBreakMode=\"WordWrap\" \n                        Width=\"1*\"\/>\n                &lt;igGrid:NumericColumn PropertyPath=\"UnitPrice\"\n                        HeaderText=\"Unit Price\"\n                        MinFractionDigits=\"2\"\n                        Width=\"1*\"\/>\n            &lt;\/igGrid:XamDataGrid.Columns>\n        &lt;\/igGrid:XamDataGrid>\n    &lt;\/Grid>\n\n&lt;\/ContentPage><\/pre>\n\n\n\n<p>In the XAML, we&#8217;ve defined a <code>XamDataGrid<\/code> 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 <code>tracks<\/code> table that this would get pretty crowded.<\/p>\n\n\n\n<p>Ok then, so how do we bind the grid against the SQLite table? First we need to create a connection to talk to the SQLite database:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW hljs\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">_connection = new SQLiteAsyncConnection(dbPath);<\/pre>\n\n\n\n<p>Where dbPath is the file path to the SQLite database that we passed around earlier. Then, we just need to create a SQLiteVirtualDataSource, configure it and assign it to the grid:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW hljs\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">var dataSource = new SQLiteVirtualDataSource();\ndataSource.Connection = _connection;\ndataSource.TableExpression = \n    \"tracks left outer join albums on tracks.AlbumId = albums.AlbumId\";\ndataSource.ProjectionType = typeof(Track);\n\ngrid.ItemsSource = dataSource;<\/pre>\n\n\n\n<p>Here we:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Provide the connection we created to the virtual data source.<\/li>\n\n\n\n<li>Provide a table expression to the virtual data source, to indicate which table to pull data from.<\/li>\n\n\n\n<li>Indicate the POCO type we created to hydrate the data rows.<\/li>\n<\/ul>\n\n\n\n<p>In the <code>TableExpression<\/code> we simply could have provided <code>tracks<\/code>, 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 <code>AlbumTitle<\/code> property.<\/p>\n\n\n\n<p>And that&#8217;s it! If you run the app, you&#8217;ll see that you can scroll through the table as if it is just one long contiguous set of records. In reality, though, only a fraction of the table is in memory on the device at once. You may have trouble scrolling fast enough to see a scenario where you get to some records before they have loaded, because the grid actually predictively loads them under the covers. Here&#8217;s what it will look like:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/users.infragistics.com\/gmurray\/Blogs\/Images\/SQlite\/SQLScroll.gif\" alt=\" Here's what it will look like\" title=\"Here's what it will look like\"\/><\/figure>\n\n\n\n<p><a href=\"https:\/\/youtu.be\/MQgu2MFN9L8\" target=\"_blank\" rel=\"noopener noreferrer\">  <\/a><\/p>\n\n\n\n<p>You can see the grid playing catch up, though, if you change the sort of the grid by tapping the column headers. This causes the current client side data to be invalidated, and new data, sorted as you requested, to be fetched, but, again, only as much as necessary.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" class=\"wp-block-heading\" id=\"adding-some-filtering\">Adding Some Filtering<\/h2>\n\n\n\n<p>Ok, let&#8217;s take that and make things a bit fancier, shall we? First, add this to the grid in the XAML for the page:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW hljs\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">&lt;StackLayout Orientation=\"Horizontal\" Grid.Row=\"1\">\n    &lt;Label Text=\"Filter\" \/>\n    &lt;Entry TextChanged=\"Entry_TextChanged\" WidthRequest=\"300\" \/>\n&lt;\/StackLayout><\/pre>\n\n\n\n<p>That markup has added an entry field so that we can collect a filter value by which to filter the table we are displaying. An event fires whenever the text of the entry has changed. So let&#8217;s add the handler for that to the code behind:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW hljs\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">private void Entry_TextChanged(object sender, TextChangedEventArgs e)\n{\n    if (String.IsNullOrEmpty(e.NewTextValue))\n    {\n        grid.FilterExpressions.Clear();\n    }\n    else\n    {\n        grid.FilterExpressions.Clear();\n        grid.FilterExpressions.Add(FilterFactory.Build(\n            (f) =>\n            {\n                return f.Property(\"Name\").Contains(e.NewTextValue)\n                .Or(f.Property(\"AlbumTitle\").Contains(e.NewTextValue))\n                .Or(f.Property(\"Composer\").Contains(e.NewTextValue));\n            }));\n    }\n}<\/pre>\n\n\n\n<p>This code will clear the grids filters if the entry field becomes blank, but otherwise will build a filter to see if Name or AlbumTitle or Composer match the provided string and make sure that filter gets used in the queries passed to SQLite.<\/p>\n\n\n\n<p>Here&#8217;s what the sample looks like now:&nbsp;<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" src=\"https:\/\/users.infragistics.com\/gmurray\/Blogs\/Images\/SQlite\/SQLFilter.gif\" alt=\" \"\/><\/figure>\n\n\n\n<p><a href=\"https:\/\/youtu.be\/MQgu2MFN9L8\" target=\"_blank\" rel=\"noopener noreferrer\">  <\/a><\/p>\n\n\n\n<p>As you can see, every time you type a letter, the local grid will need to refresh its content with the new filtered content, which you can then scroll through, in its entirety.&nbsp;<\/p>\n\n\n\n<p>You can learn more by checking out our &#8220;<a href=\"\/products\/xamarin\/write-fast\" target=\"_blank\" rel=\"noopener noreferrer\">Write Fast<\/a>&#8221; and &#8220;<a href=\"\/products\/xamarin\/run-fast\" target=\"_blank\" rel=\"noopener noreferrer\">Run Fast<\/a>&#8221; lessons and videos. You&#8217;ll also want to be sure to <a href=\"\/products\/ultimate\/download?p=xamarin\" target=\"_blank\" rel=\"noopener noreferrer\">download a free trial<\/a> of&nbsp;<a href=\"\/products\/xamarin\" target=\"_blank\" rel=\"noopener noreferrer\">Infragistics Ultimate UI for Xamarin<\/a>.&nbsp;<\/p>\n\n\n\n<p><em><strong>Graham Murray<\/strong> is a software architect and author. He builds high performance cross-platform UI components for Infragistics,&nbsp;spanning desktop, web, and mobile. Follow him on Twitter at&nbsp;<a href=\"http:\/\/www.twitter.com\/the_graham\" target=\"_blank\" rel=\"noopener noreferrer\">@the_graham<\/a>.<\/em><\/p>\n","protected":false},"excerpt":{"rendered":"<p>If you\u2019ve already worked with Infragistics&#8217; Xamarin.Forms\/Xamarin.Android\/Xamarin.iOS DataGrid (XamDataGrid) you\u2019ll have discovered it knows some very neat tricks.<\/p>\n","protected":false},"author":72,"featured_media":2370,"comment_status":"publish","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[17],"tags":[],"class_list":["post-707","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-how-to"],"_links":{"self":[{"href":"https:\/\/www.infragistics.com\/blogs\/wp-json\/wp\/v2\/posts\/707","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.infragistics.com\/blogs\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.infragistics.com\/blogs\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.infragistics.com\/blogs\/wp-json\/wp\/v2\/users\/72"}],"replies":[{"embeddable":true,"href":"https:\/\/www.infragistics.com\/blogs\/wp-json\/wp\/v2\/comments?post=707"}],"version-history":[{"count":3,"href":"https:\/\/www.infragistics.com\/blogs\/wp-json\/wp\/v2\/posts\/707\/revisions"}],"predecessor-version":[{"id":2521,"href":"https:\/\/www.infragistics.com\/blogs\/wp-json\/wp\/v2\/posts\/707\/revisions\/2521"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.infragistics.com\/blogs\/wp-json\/wp\/v2\/media\/2370"}],"wp:attachment":[{"href":"https:\/\/www.infragistics.com\/blogs\/wp-json\/wp\/v2\/media?parent=707"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.infragistics.com\/blogs\/wp-json\/wp\/v2\/categories?post=707"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.infragistics.com\/blogs\/wp-json\/wp\/v2\/tags?post=707"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}