Using a SQLite Database with Xuni iOS
A common concern for many data driven apps is whether to store data locally on device or have data available via webservice. Both options can potentially be valid choices depending on your application. While we've covered fetching and parsing web data on iOS we haven't spent as much time on data stored locally, particularly in a database. Apple provides some libraries for interacting with SQLite databases on iOS, and in this article we'll explore using a SQLite database with several Xuni controls.
General App Roadmap
Before we get started, it's best to go through the overall design of our application. The app will be based around a simple sales database with both some sales data and a list of employees. The app will start with an initial screen that contains a list of all of the employees displayed in a FlexGrid. The user should then be able to select an employee row and either edit that data or display a chart of that employee's sales performance alongside the average sales performance.
Creating a SQLite Database
Apple pre-installs a version of SQLite on the Mac which we can use to get started. The command line version of SQLite can be run via the Terminal application, and we'll getting started by using the following command: sqlite3 sampleSales.sql This will both start the SQLite command line interface and create a new database called sampleSales. We can execute SQL statements inside this interface to work with the database which we just created. The DB schema will be relatively simple for the purposes of our application. We'll only have two tables for this sample, one to store some employee information and another to store some sales information. Creating the two tables is straightforward, and we can do so by inputting the following SQL commands into the command line: CREATE TABLE employee(employeeID integer primary key autoincrement, firstname text, lastname text, location text); CREATE TABLE sales(employeeID integer primary key autoincrement, q1 integer, q2 integer, q3 integer, q4 integer, FOREIGN KEY (employeeID) REFERENCES employee(employeeID)); Inserting records into the database is also easily accomplished with commands similar to the following: INSERT INTO employee(firstname, lastname, location) VALUES ('Bill', 'Williamson', 'United States'); INSERT INTO sales(q1, q2, q3, q4) VALUES ('7654', '8098', '2396', '7912'); Once we've finished adding records to the database (and tested any other SQL statements we may want to use) we can close our of the interface by using the command .exit or .quit.
Getting Started in Xcode
Moving over to Xcode, we can create a new project to get started. We'll import in the Xuni Frameworks we'll be using (XuniCore, XuniChartCore, FlexChart, and FlexGrid) in the Target-> General tab. We'll also need import in another library to handle working with SQLite called libsqlite3.tbd. The database file should now be copied into the Xcode project (you can drag and drop it into the project). A small dialog box should open where we need to make sure the database is copied and added to the target.
Creating a Database Manager Class
We'll create another Cocoa Touch class to handle connecting to and querying the database. The class will generically be called DatabaseManager. We'll add a few properties and public methods to the header as well as import sqlite3.h from the library which we added to the project earlier.
Next, we can start working on the implementation file. We'll add some private properties and methods to the interface that out public methods will use internally. These properties and methods mostly relate to the database's name and location, mechanisms for querying it, and somewhere to store the results. Also note there is a runExecutableQuery and a runNonExecutableQuery method. An executable query is something that will change the database (insert, delete, add, etc.). A non-executable query would be something along the lines of a select statement where we are only returning a result.
The initWithDatabaseFilename allows us to use a string to specify which database we want to use. If the database does not exist in the documents directory than the copyDatabaseIntoDocumentsDirectory copies it from the main bundle to the documents.
As mentioned earlier, the runNonExecutableQuery will work with a read only operation from the database which we'll later use when we supply select queries to return the result sets that we'll visualize in the app. Basically we'll be using some supplied SQLite objects and methods to open a connection to the database, feed the database our query, loop through the results, and then close the connection.
The runExecutable method is similar, but we'll use it after editing the data to insert our changes back into the database. The code is actually a little more simple since we aren't returning any results, but instead noting success or failure.
Finally, we need to add to our public methods which use the private runQuery methods that we just created. The loadDataFromDB method will later be used to get the results that we'll visualize with our Xuni controls, and the executeQuery method which we'll use when editing the data.
Creating Data Models for the Controls
Since the database just returns an array of the results, we'll need to create models to package this data for the controls. Our GridModel is going to have properties for each of the columns we'll attach to the FlexGrid. The generateGridArray method takes the results array that the database has returned and iterates through it to create GridModel objects. The objects are added to a NSMutableArray which will eventually be returned to connect to the itemsSource of our FlexGrid. The header:
And the implementatin:
The ChartModel is similar, but we're concerned with less data here. Basically, we only need to worry about the quarter we're in, the employee's sales for that quarter, and the average sales for the quarter. The generateChartArray method just packages the database results into this form. Eventually a NSMutableArray will be returned which we can connect to the itemsSource of our FlexChart later on. The header:
And the implementation:
ViewControllers
I'm not going to spend as much time on the view controllers since they're mostly by the numbers (you can look at the included sample if you want to see the full code), but there are a few things that will be unique with regards to interacting with the database. We'll need to import the DatabaseManager class into each of our ViewControllers, and declare a databaseManager property. The usage for the DatabaseManager follows this general pattern:
You'll notice I'm calling the loadData method in the above code. There will be an implementation of this method in each ViewController, and it will vary between each since this is where we'll specify the query that returns the data we interact with. In the GridViewController, this is a relatively straightforward select statement:
The loadData method of the ChartViewController is a bit more complicated as we're dealing with the union of a specific result for an employee as well as a result that's the average of all records. It's also important that we explicitly preserve a specific order here so the average is always the first result returned and the specific employee sales is always the second.
Finally, the loadData for the EditViewController is probably the most straightforward implementation as it is a simple join statement that returns all of the individual fields for editing.
Clicking the commit button on the EditViewController's screen is the single instance in our app where we'll be using an executable query. Here, we'll update both tables with the modifications that we've made.
When the full app is complete it allows you to navigate easily between several different visualizations of the data.
Conclusion
There are many possibilities for adding on to this structure, but this example should provide a basic idea of how a project using a SQLite database could work. SQLite is relatively easy to work with, and all of the tools you need are included.