An iOS tutorial and associated library for keeping an UICollectionView in sync with a dynamic SQL data model.
Branch: master
Clone or download
Adam Langley Adam Langley
Adam Langley and Adam Langley Updated gitignore
Latest commit 5c96181 May 10, 2018
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
DifferentialCollections Renamed some classes for readability. Mar 10, 2018
Tutorial Refactor Mar 10, 2018
.gitignore Updated gitignore May 9, 2018
DifferentialCollections.sln
DifferentialCollections.userprefs Renamed some classes for readability. Mar 10, 2018
LICENSE
README.md

README.md

DifferentialCollections

A Xamarin iOS library for keeping a UICollectionView in sync with a dynamic SQL data model.

Install-Package Dabble.DifferentialCollections

With a single line of code, initiate the synchronization between a UICollectionView and a highly dynamic data source of thousands of records, avoiding any complex, application-specific messaging - while supporting request pagination, animations, and persistent cell-selection.

void TxtSearch_TextChanged(object sender, UISearchBarTextChangedEventArgs e)
{
    // Tell the Differential Collection to requery the SQL data-source, calculate and push all the changes
    // to the collection view, in a non-destructive manner.
    _cryptoCoinDataSource.RequeryWithCriteria(x =>
    {
        x.FilterString = txtSearch.Text;
    });
}

The result (see Milestone2 project in the Tutorials folder), is a collection-view update that

  1. Removes rows that have disappeared from the result-set.
  2. Adds rows that have appeared in the result-set.
  3. Moves the positions of rows that now fall in a different location in the result-set.
  4. Reloads rows that have changed in the database since they were last displayed.
  5. Retains selection as rows move about.
  6. Avoids any 'janky' visual experience!

Final Demo App

Demos

Video Explanation
Deletion Demo Deletion of a random row from SQLite table.
var count = _cryptoCoinDataSource.Count; 
var random = _cryptoCoinDataSource.GetPage(Random.Next(count - 1), 1).FirstOrDefault();
if (null != random)
{
AppDelegate.Connection.BeginTransaction();
AppDelegate.Connection.Delete(random);
AppDelegate.Connection.Commit();
// This is the magic line, request re-sync of the view
_cryptoCoinDataSource.Requery();
}
Insertion Demo Insertion of a random row to SQLite table
var newName = "Bitcoin " + RandomString(8);
var newCoin = new CryptoCoin
{
Id = newName,
Name = newName,
PriceUSD = Random.NextDouble() * 2,
TwentyFourHourChange = Random.NextDouble(),
};
AppDelegate.Connection.BeginTransaction();
AppDelegate.Connection.InsertOrReplace(newCoin);
AppDelegate.Connection.Commit();
// This is the magic line, request re-sync of the view
_cryptoCoinDataSource.Requery();
Move Demo Modification of 2 random SQLite table rows to demonstrate them exchanging positions
// pick 2 random rows, and swap their 24 hour change values
var count = _cryptoCoinDataSource.Count;
if (count < 2)
return;
var randomIndexA = Random.Next(count - 1);
var randomIndexB = 0;
do
{
randomIndexB = Random.Next(count - 1);
} while (randomIndexB == randomIndexA);

var randomA = _cryptoCoinDataSource.GetPage(randomIndexA, 1).FirstOrDefault();
var randomB = _cryptoCoinDataSource.GetPage(randomIndexB, 1).FirstOrDefault();
if (null != randomA && null != randomB)
{
AppDelegate.Connection.BeginTransaction();

var temp = randomA.TwentyFourHourChange;
randomA.TwentyFourHourChange = randomB.TwentyFourHourChange;
randomB.TwentyFourHourChange = temp;

// bump version field so UI can locate changed rows.
randomA.Version++;
randomB.Version++;

// commit to database
AppDelegate.Connection.UpdateAll(new []{
randomA, randomB
});

AppDelegate.Connection.Commit();

// This is the magic line, request re-sync of the view
_cryptoCoinDataSource.Requery();
}

How do I use it?

Follow these steps - the example class names here are referencing the 'Milestone 2' example project.

  1. Derive DifferentialDataModel<TPrimaryKey, TTableModel, TTableCriteria>.
    This class executes the appropriate queries to supply paginated results, and row meta-data to the DifferentialCollectionViewSource.
    CryptoCoinDataSource in the example.

    • Implement GetCount to count the number of table records matching your criteria.
    • Implement GetRowMeta to return the row position and version of a set of records matching the supplied list of primary keys.
    • Implement GetIds to return a list of primary keys for a page of records.
    • Implement GetPage to return a page of fully-populate records that will be passed into your cells for display.
  2. Implement TTableCriteria.
    This class encapsulates all query variation that you will want to perform, if the user will 'search' for example, this class needs needs a property to hold the search string.
    CryptoCoinCriteria in the example.

  3. Derive DifferentialCollectionViewSource<TPrimaryKey, TTableModel, TTableCriteria>.
    This class receives the message that a model object has been pulled from the database, and lets you bind it into the widgets in a cell via OnDataContextLoaded
    CryptoCoinCollectionViewSource in the example.

  4. Wire it up in your ViewController.

    • Instantiate your DifferentialCollectionViewDataSource
    • Instantiate your DifferentialCollectionViewSource
    • Assign the DifferentialCollectionViewDataSource to the DataModel property of your DifferentialCollectionViewSource
    • Assign the DifferentialCollectionViewSource to the Source property of your UICollectionView

Any time you change your underlying database, call your DifferentialCollectionViewDataSource.Requery() to refresh the collection view. If you also want to change the criteria used to populate your view, pass it to the RequeryWithCriteria:

_cryptoCoinDataSource.RequeryWithCriteria(x =>
{
  x.FilterString = txtSearch.Text;
});