At some point in your mobile development career, you are going to need to deal with data. Dealing with data means more than processing and displaying information to the end user. You are going to need to store this information somewhere and be able to get at it easily. Thanks to Xamarin and open source software, you can easily store your data with an industry tested platform, SQLite.
1. Storing Data
So why do you need to care about data when it comes to your app? Because it is all around you. You can't escape it. No matter what kind of app you are writing, whether it's a game or some sort of utility, you are going to need to store data at some point. That data could be user data, statistics, or anything else of interest that either you or the user will be interested in at some point in the use of your app.
At this point, let's assume that you have decided to go the Xamarin.Forms route, because you are interested in targeting several platforms not only in the logic for your app, but also for the user interface layer.
Great. But what do you do now that you need to store information within your app? Don't worry, there's a very simple solution to this problem, SQLite.
2. Introduction to SQLite
You have now seen the term SQLite a couple of times in this tutorial, it's time to get down to the meat. What exactly is SQLite? SQLite is a public domain, zero configuration, transactional SQL database engine. All this means is that you have a full featured mechanism to store your data in a structured way. Not only do you get all of this, you also have access to the source code, because it's open source.
We will not be covering all the features of SQLite in this tutorial simply because there are too many to go through. Rest assured that you will have the ability to easily create a table structure to store data and retrieve it in your app. These are the concepts that we will be focusing on in this tutorial.
In the world of Xamarin.Forms, SQLite is a natural fit for a very simple reason. The SQLite engine is readily available on both iOS and Android. This means that you can use this technology right out of the box when you choose to write a Xamarin.Forms app.
Getting access to SQLite functionality in Windows Phone apps requires one additional step that we will go over a little later. All this functionality and cross platform accessibility is great, but how will we get access to the native platform implementations from our C# code in Xamarin.Forms? From a nice NuGet package, that's how. Let's take a look.
3. Creating an App
Let's start by creating a simple Xamarin.Forms application. In this tutorial, I will be using a Mac running Xamarin Studio, but you can just as easily be using Xamarin Studio or Visual Studio running on a PC.
Step 1: Create a Project
We start the process by creating a new Xamarin.Forms app. To do this, simply select the Mobile Apps project template family on the left and choose one of the Xamarin.Forms templates on the right. You can use either the PCL or Shared version of the template, but for this case, I will be using the PCL. You can follow along using either one, but there will be a slight difference if you choose the Shared template later on.
You can give the project any name you like. I will call this project IntroToSQLite. After you click the OK button, your IDE will go through the process of creating your solution. Your solution will contain four projects:
- IntroToSQLite - PCL project
- IntroToSQLite.Android - Android project
- IntroToSQLite.iOS - iOS project
- IntroToSQLite.WinPhone - Windows Phone project (only on a PC)
Step 2: Add SQLite Support
Now that we have our basic project structure set up, we can start to add access to SQLite to our PCL project. We need to install a new package into our project named SQLite.Net. This is a .NET wrapper around SQLite that will allow us to access the native SQLite functionality from a Xamarin.Forms PCL or Shared project.
We access this NuGet package by right-clicking on either Packages or References, depending on which IDE you are using, and select Add Package (or Reference). In the search box, type sqlite.net. This will show you a rather large collection of packages that you can include in your project.
Since I chose to go the PCL route for my Xamarin.Forms project, I will need to select the SQLite.Net PCL package to include into my project. Which one do you choose if you went the Shared project route? None.
SQLite and Shared Projects
If you've chosen the Shared project template earlier in the tutorial, you may be wondering how to get access to the SQLite package. The short answer is that you can't. If you remember from a previous tutorial, you can't add references to a Shared project. To get access to SQLite from a Shared project, you simply add the source code to the project.
Add Some Code
The final step in adding SQLite functionality to the PCL project is to create an interface that will allow us access into the SQLite world. The reason we are doing this is because we need to access the native functionality on the different platforms as we saw in a previous tutorial.
Let's start by defining an interface that is going to give us access to the SQLite database connection. Within your PCL project, create a new interface named ISQLite and replace the implementation with the following:
using System; using SQLite.Net; namespace IntroToSQLite { public interface ISQLite { SQLiteConnection GetConnection(); } }
This is the interface that we will implement and get access to via the DependencyService
from the native implementations.
Step 3: Define the Database
We now have access to the SQLite functionality, let's define our database. This particular application is going to be quite simple and we are just going to store some of our random thoughts as we come up with them.
We start by creating a class that will represent the data stored in a particular table. Let's call this class RandomThought
.
using System; using SQLite.Net.Attributes; namespace IntroToSQLite { public class RandomThought { [PrimaryKey, AutoIncrement] public int ID { get; set; } public string Thought { get; set; } public DateTime CreatedOn { get; set; } public RandomThought () { } } }
As you can see, this is a very simple class with three properties. Two of those properties are just your normal everyday properties, Thought
and CreatedOn
. These two properties are going to represent columns in the SQLite database, which will contain a table named RandomThought
. The third property, ID
, is also going to represent a column within the table and contain a unique id that we can use to refer to a specific RandomThought
row within the table.
The interesting thing about the ID
property is that it is decorated with two attributes, PrimaryKey
and AutoIncrement
. PrimaryKey
tells SQLite that this column is going to be the primary key of the table, which means that, by default, it needs to be unique and there is an index applied to it to speed up retrievals from this table when referring to a row by this column.
AutoIncrement
means that, when we insert a new RandomThought
into this table, the ID
column will be populated automatically with the next available integer value. The next step is to create this table in the database.
I like to create a class that represents my database and keep all the logic to access the database and its tables within this class. For this, I will create a class named RandomThoughtDatabase
:
using System; using SQLite.Net; using Xamarin.Forms; using System.Collections.Generic; using System.Linq; namespace IntroToSQLite { public class RandomThoughtDatabase { private SQLiteConnection _connection; public RandomThoughtDatabase () { _connection = DependencyService.Get<ISQLite> ().GetConnection (); _connection.CreateTable<RandomThought> (); } public IEnumerable<RandomThought> GetThoughts() { return (from t in _connection.Table<RandomThought> () select t).ToList (); } public RandomThought GetThought(int id) { return _connection.Table<RandomThought> ().FirstOrDefault (t => t.ID == id); } public void DeleteThought(int id) { _connection.Delete<RandomThought> (id); } public void AddThought(string thought) { var newThought = new RandomThought { Thought = thought, CreatedOn = DateTime.Now }; _connection.Insert (newThought); } } }
This is a very simple implementation as it only contains a few methods. These are typically some of the basic operations you perform when dealing with a database. One point of note is the constructor. Within the constructor we are doing two things.
First, we are using the DependencyService
class to get a registered class that implements the ISQLite
interface and call its GetConnection
method.
Second, we use the CreateTable
method on the SQLiteConnection
class to create a table called RandomThought
. This method will create the table, if it doesn't already exist, and exit gracefully if it already exists.
Obviously, you can get as sophisticated with this class as you want by adding all sorts of functionality, but these operations are typically a good starting point.
Step 4: Add the iOS Implementation
Most of the code that we use to interact with the database is going to be found in the PCL (or Shared) project. But we still need to do a little wiring up in the native implementations to get everything working correctly.
The main obstacle that we need to work around on the native side when using SQLite is where we are going to store the actual database file. This differs from platform to platform. Here is what we need for iOS.
Before we can actually add any sort of SQLite functionality to the iOS project, we need to add the SQLite.Net PCL as well as the SQLite.NET PCL - XamarinIOS Platform packages to this project. You can follow the same steps that you took in Step 2, making sure to add both to the project. Once you have added this package, you can start to write some SQLite code within the iOS project.
Let's create an implementation of the ISQLite
interface for iOS. Start by creating a new class, naming it SQLite_iOS
.
using System; using System.IO; using SQLite; using IntroToSQLite.iOS; using Xamarin.Forms; [assembly: Dependency(typeof(SQLite_iOS))] namespace IntroToSQLite.iOS { public class SQLite_iOS: ISQLite { public SQLite_iOS () { } #region ISQLite implementation public SQLite.Net.SQLiteConnection GetConnection () { var fileName = "RandomThought.db3"; var documentsPath = Environment.GetFolderPath (Environment.SpecialFolder.Personal); var libraryPath = Path.Combine (documentsPath, "..", "Library"); var path = Path.Combine (libraryPath, fileName); var platform = new SQLite.Net.Platform.XamarinIOS.SQLitePlatformIOS (); var connection = new SQLite.Net.SQLiteConnection (platform, path); return connection; } #endregion } }
We get access to the correct location to store the database file, create a new SQLiteConnection
object, and pass it back to our PCL (or Shared) project. The assembly attribute at the top of the file is used to identify this class as a Dependency
that can be retrieved via the Get
method on the DependencyService
class.
Step 5: Add the Android Implementation
This step is very similar to the previous one. The only difference is that the code will change a little due to the fact that the location of the database file will be different. You will still need to add the appropriate packages to the Android project (SQLite.Net PCL and SQLite.NET PCL - XamarinAndroid) as you did before. Once you have completed that, you can add the appropriate code in a new class named SQLite_Android
.
using System; using System.IO; using Xamarin.Forms; using IntroToSQLite.Android; [assembly: Dependency(typeof(SQLite_Android))] namespace IntroToSQLite.Android { public class SQLite_Android: ISQLite { public SQLite_Android () { } #region ISQLite implementation public SQLite.Net.SQLiteConnection GetConnection () { var fileName = "RandomThought.db3"; var documentsPath = Environment.GetFolderPath (Environment.SpecialFolder.Personal); var path = Path.Combine (documentsPath, fileName); var platform = new SQLite.Net.Platform.XamarinAndroid.SQLitePlatformAndroid (); var connection = new SQLite.Net.SQLiteConnection (platform, path); return connection; } #endregion } }
You now have a working implementation of the ISQLite interface from the perspective of your Android app.
Step 6: Add the Windows Phone Implementation
Since I am running this app from a Mac, I won't be creating the Windows Phone implementation, but if you would like to do this, you can.
The first step is to add support to your Windows Phone project for SQLite. As mentioned earlier, SQLite comes by default on iOS and Android. This is not true for Windows Phone, but it is supported. To get it installed, you can follow the instructions found on the Xamarin website.
After installing SQLite, the process of adding the functionality for Windows Phone will be almost exactly the same, except that the packages to install are SQLite.Net PCL and SQLite.Net PCL - WindowsPhone 8 Platform. With these packages installed, you can create the Windows Phone implementation of the ISQLite
interface.
using System; using System.IO; using Xamarin.Forms; using IntroToSQLite.WinPhone; [assembly: Dependency(typeof(SQLite_WinPhone)] namespace IntroToSQLite.WinPhone { public class SQLite_WinPhone: ISQLite { public SQLite_WinPhone () { } #region ISQLite implementation public SQLite.Net.SQLiteConnection GetConnection () { var fileName = "RandomThought.db3"; var path = Path.Combine (ApplicationData.Current.LocalFolder.Path, fileName); var platform = new SQLite.Net.Platform.WindowsPhone8.SQLitePlatformWP8 (); var connection = new SQLite.Net.SQLiteConnection (platform, path); return connection; } #endregion } }
There you have it. Now you have all of your native implementations complete. It's time to give this app a user interface and get your data into the database.
Step 7: Adding the User Interface
Since this tutorial is well into the topic of Xamarin.Forms, I'm going to assume that you at least have a basic working knowledge of Xamarin.Forms. With this assumption in mind, I'm not going to go into a lot of detail on the process of creating the user interface. If you need more background information on Xamarin.Forms, check out my other Xamarin.Forms tutorials on Tuts+.
The user interface is going to consist of two separate pages. The first page will contain a list of all the thoughts we have entered in a list while the second page will let the user enter a new thought. Let's build these pages.
Create the ListView
We will first focus on creating the first page that will contain a list of RandomThought
objects. Start by creating a new file in the PCL (or Shared) project and name it RandomThoughtsPage
. Replace the default implementation with the following:
using System; using Xamarin.Forms; namespace IntroToSQLite { public class RandomThoughtsPage: ContentPage { private RandomThoughtDatabase _database; private ListView _thoughtList; public RandomThoughtsPage (RandomThoughtDatabase database) { _database = database; Title = "Random Thoughts"; var thoughts = _database.GetThoughts (); _thoughtList = new ListView (); _thoughtList.ItemsSource = thoughts; _thoughtList.ItemTemplate = new DataTemplate (typeof(TextCell)); _thoughtList.ItemTemplate.SetBinding (TextCell.TextProperty, "Thought"); _thoughtList.ItemTemplate.SetBinding (TextCell.DetailProperty, "CreatedOn"); var toolbarItem = new ToolbarItem { Name = "Add", Command = new Command(() => Navigation.PushAsync(new ThoughtEntryPage(this, database))) }; ToolbarItems.Add (toolbarItem); Content = _thoughtList; } public void Refresh() { _thoughtList.ItemsSource = _database.GetThoughts (); } } }
Most of the work done in this class is in the constructor. The constructor allows us to pass in an instance of the RandomThoughtsDatabase
to get all the saved thoughts. We set the Title
property of the page to "Random Thoughts", retrieve all the existing thoughts, create a new instance of a ListView
, and create a ToolbarItem
that will allow us to click a button to bring up the entry page. We haven't implemented that yet, but we will shortly.
To get our new RandomThoughtsPage
up on the screen, we need to make a little modification to the App.cs file. Within this file, modify the GetMainPage
method to look like the following:
public static Page GetMainPage () { var database = new RandomThoughtDatabase (); return new NavigationPage (new RandomThoughtsPage (database)); }
The GetMainPage
method now creates a new instance of our RandomThoughtDatabase
class and returns a new instance of the RandomThoughtsPage
. With this change, our iOS and Android apps should look something like this:
Create the Entry Page
We now have a list page for all of our RandomThought
objects, but we don't have a way to enter new ones. For that, we will create another page similar to the previous page. Create a new file in your PCL (or Shared) project and call it ThoughtEntryPage
. Replace the default implementation with the following:
using System; using Xamarin.Forms; namespace IntroToSQLite { public class ThoughtEntryPage: ContentPage { private RandomThoughtsPage _parent; private RandomThoughtDatabase _database; public ThoughtEntryPage ( RandomThoughtsPage parent, RandomThoughtDatabase database) { _parent = parent; _database = database; Title = "Enter a Thought"; var entry = new Entry (); var button = new Button { Text = "Add" }; button.Clicked += async (object sender, EventArgs e) => { var thought = entry.Text; _database.AddThought(thought); await Navigation.PopAsync(); _parent.Refresh(); }; Content = new StackLayout { Spacing = 20, Padding = new Thickness(20), Children = { entry, button }, }; } } }
In this class, all the work is done within the constructor. We get a reference to the parent page, RandomThoughtsPage
, as well as the database. The rest is basic setup code with an Entry
object for entering text and a Button
.
Once the user taps the Button
, we use the database to add the new thought, dismiss the page, go back to the list page, and call the Refresh
method to update the ListView
. Once this is all wired up, we can run it to actually enter some values.
Entering Thoughts
Here is what it looks like on iOS and Android to enter some of your thoughts:
Viewing the List
After you have entered a few thoughts, your list will look something like this:
Conclusion
There you have it. You now have the ability to add database functionality to your Xamarin.Forms app to store and retrieve any sort of data with ease.
To continue your learning journey with Xamarin.Forms and SQLite, I give you the following challenge. See if you can enhance this application to enable deleting thoughts and update the list page in a similar fashion as the entry page. Good luck and happy coding.