Adobe AIR bundles the popular SQLite database to serve your application’s relations data storage needs. This makes it simple for applications to persist complex data across sessions.
If you are creating an application for your website, this will make it simple to synchronize data form your website’s SQL database (assuming you are using a relational database) to your app for offline operation. Even otherwise storing data in a structured manner in a relational database has many benefits as you may be aware.
We will build a simple application that implements a TODO list storing the data in an SQL database. The application will have an uncomplicated interface. The image of interface below should be self-explanatory:
All we have is a simple list of to-do items. A small textbox and add button allow one to add items to the list. Items in the list can be removed by selecting them and pressing the delete key. Simple!
Before we begin though, let us take a look at the SQL database API in AIR. We will only take a look at the relevant portions, Adobe AIR provides numerous other features such as encryption, indices etc. Note that we will not be covering the semantics of SQL itself, so we will assume you have at least a cursory knowledge of the same. A good resource for learning the SQL syntax is here.
The SQL database API in AIR supports both synchronous and asynchronous modes of operation. While the synchronous mode is simpler to understand and implement, a long synchronous operation means that your UI is blocked and non-responsive while the data is being fetched. In synchronous mode, as expected, you get a response immediately after an SQL statement has been executed.
In asynchronous mode, you need a call-back function to respond to the data once it has been retrieved from the database. This way your application can continue updating and working while data is being retrieved, added or updated in the database.
For further gains in speed, you can create a database that is stored only in memory, instead of a file. This can be useful if you don’t need the data to persist across multiple sessions with the app.
To work with the database, you must initialize a new SQLConnection object. The SQLConnection object will allow us to open a connection to a database using the openAsync (for asynchronous operation) or open functions (for synchronous operation). This function needs to be provided a File instance in which it will store the data – this file will be created if it doesn’t already exist.
var dbConnection:SQLConnection = new SQLConnection();var dbFile:File = File.applicationDirectory.resolvePath("dbfile.db");dbConnection.open(dbFile);
If we had provided null instead of dbFile in the dbConnection.open function, the database would be stored in memory instead.
If we use the openAsync function instead of open, the database will be opened in the Asynchronous mode, and you will have to add event listeners to dbConnection to know when it is safe to operate on the database.
Once you have your database set and ready, you can begin creating new statements to execute on your database. When you create a statement once, it is compiled, and as long as you don’t change the statement text, you stand to get better performance at each subsequent execution.
SQL statements need to be associated with a SQLConnection before they are executed. The following is an example of a simple SQL statement being executed:
var createTableStmt:SQLStatement = new SQLStatement();createTableStmt.sqlConnection = dbConnection;createTableStmt.text = "CREATE TABLE data (id INTEGER PRIMARY KEY AUTOINCREMENT, data TEXT)";createTableStmt.execute();
If you execute a statement that is supposed to return data, then you can access that data using the getResult() function of the SQLStatement. If you opened the database Asynchronously, then this value will only be available after the SQLEvent.RESULT event has occurred, and you will need to add an event listener for the event.
Our todo app requires a single table with a single column to store our todo entries, and column for the id. We create a class to perform all the data access and manipulation operation.
Since we have already covered the basics of the SQL database API, let’s dive into our TodoData class that we will use to manipulate the database:
package{ import flash.data.SQLConnection; import flash.data.SQLStatement; import flash.filesystem.File; import mx.collections.ArrayCollection; public class TodoData { private var dbConnection:SQLConnection; private var getTodosStmt:SQLStatement; private var addTodoStmt:SQLStatement; private var delTodoStmt:SQLStatement; public function TodoData(databaseFile:File) { dbConnection = new SQLConnection(); dbConnection.open(databaseFile); initializeDB(); getTodosStmt = new SQLStatement(); getTodosStmt.sqlConnection = dbConnection; getTodosStmt.text = "SELECT * FROM todos "; addTodoStmt = new SQLStatement(); addTodoStmt.sqlConnection = dbConnection; addTodoStmt.text = "INSERT INTO todos (todo) VALUES (?) "; delTodoStmt = new SQLStatement(); delTodoStmt.sqlConnection = dbConnection; delTodoStmt.text = "DELETE FROM todos WHERE id = ? "; } private function initializeDB():void { var createStmt:SQLStatement = new SQLStatement(); createStmt.sqlConnection = dbConnection; createStmt.text = "CREATE TABLE IF NOT EXISTS todos (id INTEGER PRIMARY KEY AUTOINCREMENT, todo TEXT) "; createStmt.execute(); } public function getAll():ArrayCollection { getTodosStmt.execute(); return new ArrayCollection(getTodosStmt.getResult().data); } public function add(todo:String):void { addTodoStmt.clearParameters(); addTodoStmt.parameters[0] = todo; addTodoStmt.execute(); } public function remove(id:int):void { delTodoStmt.clearParameters(); delTodoStmt.parameters[0] = id; delTodoStmt.execute(); } }}
A quick overview of what we are doing here:
Now for the rest of our applications code in the mxml file.
<?xml version="1.0" encoding="utf-8"?><s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" showStatusBar="false" width="250" height="430" creationComplete="windowedapplication1_creationCompleteHandler(event)" > <s:layout> <s:VerticalLayout /> </s:layout> <fx:Script> <![CDATA[ import mx.collections.ArrayCollection; import mx.events.FlexEvent; private var dbFile:File = File.applicationDirectory.resolvePath("dbfile.db"); private var todos:TodoData = new TodoData(dbFile); protected function addTodo_clickHandler(event:MouseEvent):void { todos.add(newTodo.text); newTodo.text = ""; refreshTodos(); } protected function todoItems_keyDownHandler(event:KeyboardEvent):void { if (event.charCode == 127){ if (todoItems.selectedItem != null){ todos.remove(todoItems.selectedItem.id); refreshTodos(); } } } private function refreshTodos():void { todoItems.dataProvider = todos.getAll(); } protected function windowedapplication1_creationCompleteHandler(event:FlexEvent):void { refreshTodos(); } ]]> </fx:Script> <s:List id="todoItems" width="250" height="400" keyDown="todoItems_keyDownHandler(event)" dataProvider="{new ArrayCollection()}" labelField="todo" /> <s:HGroup width="100%"> <s:TextInput id="newTodo" width="100%" /> <s:Button id="addTodo" label=" " click="addTodo_clickHandler(event)" /> </s:HGroup></s:WindowedApplication>
Here is a brief description of what is going on:
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" showStatusBar="false" width="250" height="430" creationComplete="windowedapplication1_creationCompleteHandler(event)" >
<s:List id="todoItems" width="250" height="400" keyDown="todoItems_keyDownHandler(event)" dataProvider="{new ArrayCollection()}" labelField="todo" />
<s:HGroup width="100%"> <s:TextInput id="newTodo" width="100%" /> <s:Button id="addTodo" label=" " click="addTodo_clickHandler(event)" /></s:HGroup>
private var dbFile:File = File.applicationDirectory.resolvePath("dbfile.db");private var todos:TodoData = new TodoData(dbFile);
private function refreshTodos():void{ todoItems.dataProvider = todos.getAll();}protected function windowedapplication1_creationCompleteHandler(event:FlexEvent):void{ refreshTodos();}
protected function addTodo_clickHandler(event:MouseEvent):void{ todos.add(newTodo.text); newTodo.text = ""; refreshTodos();}
protected function todoItems_keyDownHandler(event:KeyboardEvent):void{ if (event.charCode == 127){ if (todoItems.selectedItem != null){ todos.remove(todoItems.selectedItem.id); refreshTodos(); } } }
This is all we need to do for this app! There are many ways to improve it; a starter would be giving the ability to edit todos. However as it is it will function as a todo app, allowing one to enter a list of todos and delete them from the list when done. The todo items will persist across sessions thanks to the database.
This is just a small taste of what you can do with a database. Using third party libraries and ORM frameworks you can take advantage of this great facility allowed by AIR.
You can download a free trial of Adobe Flash Builder 4 from the Adobe website.