Search

The Repository Pattern with Xamarin Forms and SQLite

Wednesday 20 April 2016

The official Xamarin Forms site explains how to use SQLite with a cross platform application. In this post we go a step further and use the repository pattern and dependency injection to allow for unit testing. Here's the source code for the sample app.

The App

The example app is a simple screen which lists the months of the year. It looks like this...



...and here's the code...
public class HomePage : ContentPage
{
 public HomePage ()
 {
  // Set up the repository.
  var sqlProvider = DependencyService.Get <isqlprovider> ();
  var repo = new SQLRepository (sqlProvider);
  repo.Init ();

  // Set up the controller factory and controller.
  var controllerFactory = new ControllerFactory (repo);
  var controller = controllerFactory.GetDemoController ();

  // Set up the list.
  var listView = new ListView
  {
   RowHeight = 40
  };

  listView.ItemsSource = controller.GetFirstQuarter ();
 
  Content = new StackLayout {
   VerticalOptions = LayoutOptions.FillAndExpand,
   Children = { listView }
  };
 }
}
This is simple. We use the DependencyService to get a reference to the SQLite provider (which supplies the platform specific SQLite connection). We initialise the repository. The repository is passed to the controller factory which supplies a controller. Finally we setup the page.

Platform Specific Code

Each platform needs to implement it's own ISQLProvider. This sets up and opens a SQLite connection. Here's the Android version...
public class SQLite_Android : ISQLProvider {
 public SQLite_Android () {}
 public SQLite.SQLiteConnection GetConnection () {
  var sqliteFilename = "DemoSQLite.db3";
  string documentsPath = System.Environment.GetFolderPath (System.Environment.SpecialFolder.Personal); // Documents folder
  var path = Path.Combine(documentsPath, sqliteFilename);

  File.Delete (path);

  // Create the connection
  var conn = new SQLite.SQLiteConnection(path);
  // Return the database connection
  return conn;
 }}

The Core Project and Dependency Injection

The core project is where the code to control the app lives. It's separated out from the UI layer which means we can unit test it. The key classes here are...
{
 private StandardKernel kernel;

 public ControllerFactory (IRepository repository)
 {
  kernel = new StandardKernel ();
  kernel.Bind<irepository> ().ToConstant (repository);
 }

 public DemoController GetDemoController()
 {
  return kernel.Get<democontroller> ();
 }
}
...the ControllerFactory - Responsible for building the controllers for the app. In the demo we are using dependency injection with Ninject to supply the repository to the controller. 
public class SQLRepository : IRepository
{
 SQLiteConnection connection;

 public SQLRepository (ISQLProvider provider)
 {
  this.connection = provider.GetConnection();
 }

 public void Init()
 {
  this.connection.CreateTable<month> ();
  this.connection.Insert(new Month(12, "December"));
  this.connection.Insert(new Month(11, "November"));
  this.connection.Insert(new Month(10, "October"));
  this.connection.Insert(new Month(9, "September"));
  this.connection.Insert(new Month(8, "August"));
  this.connection.Insert(new Month(7, "July"));
  this.connection.Insert(new Month(6, "June"));
  this.connection.Insert(new Month(5, "May"));
  this.connection.Insert(new Month(4, "April"));
  this.connection.Insert(new Month(3, "March"));
  this.connection.Insert(new Month(2, "February"));
  this.connection.Insert(new Month(1, "January"));
 }

 public List<month> GetMonths ()
 {
  return this.connection.Table<month> ().ToList ();
 }
}
...the SQLRepository - This is a simple demo so all the repository does is provide a list of months. Months are set up when the repository initialises.
public class DemoController
{
 private IRepository repo;

 public DemoController (IRepository repo)
 {
  this.repo = repo;
 }

 public List<month> GetFirstQuarter()
 {
  return this.repo.GetMonths ().Where (x =&gt; x.Number &lt;= 3).ToList ();
 }
}
...the DemoController - Again this is very simple in the demo app. It sorts the list from the repository and passes it back.

The Unit Test Project

The unit test project doesn't use SQLite. Instead it has its own repository which uses an in memory list. We just have one sample test here.
public class DemoController
[TestFixture ()]
public class Test
{
 [Test ()]
 public void TestCase ()
 {
  var repository = new TestRepository ();
  repository.Months.Add (new Month (12, "December"));
  repository.Months.Add (new Month (11, "November"));
  repository.Months.Add (new Month (10, "October"));
  repository.Months.Add (new Month (9, "September"));
  repository.Months.Add (new Month (8, "August"));
  repository.Months.Add (new Month (7, "July"));
  repository.Months.Add (new Month (6, "June"));
  repository.Months.Add (new Month (5, "May"));
  repository.Months.Add (new Month (4, "April"));
  repository.Months.Add (new Month (3, "March"));
  repository.Months.Add (new Month (2, "February"));
  repository.Months.Add (new Month (1, "January"));

  var controllerFactory = new ControllerFactory (repository);
  var controller = controllerFactory.GetDemoController ();
  var months = controller.GetFirstQuarter ();

  Assert.AreEqual ("February", months [1].Name);
 }
}

Project Setup & Package References

To clarify the project structure and required references we have...

The Xamarin Forms project - This provides the UI. It has to reference the sqlite.net pcl so it can pass the connection from the platform specific project to the core project.

The platform specific projects - these need to reference Ninject and sqlite.net. The platform specific versions are provided by the package dependency.

The core project - again this needs to reference Ninject and sqlite.net but this time it will use the cross compatible versions.

The unit test project - This one just needs the Ninject reference as it doesn't use sqlite.net.

The key thing to remember with all these package references is that the platform specific version will be provided; A reference to sqlite-net-pcl in an IOS project will provide the IOS version, in Android it will provide the Android version.

If you are missing the reference to Ninject in the platform specific project you will get a 'System.NotImplemented' exception.

If you are missing the SQLite reference in the platform specific project you will get the error 'Something went wrong in the build configuration.  This is the bait assembly, which is for referencing by portable libraries, and should never end up part of the app.  Reference the appropriate platform assembly instead.'

If you are getting either error with the packages in place try cleaning the solution or uninstalling the app from your device.

2 comments:

  1. Thanks for the tip on cleaning the solution! I got that 'bait assembly' exception at runtime and performing a clean fixed it.

    ReplyDelete
  2. This an informative and helpful post on the Repository Pattern with Xamarin Forms and SQLite. Good job!

    Xamarin Consultants.

    ReplyDelete