Andrew Odri

January
28
2009
There are 16 comments

Adobe AIR

I have been writing some applications in AIR that for the first time make extensive use of SQLite. I hit a few roadblocks along the way, so here are a few things that may come in handy if you are using SQLite in AIR for the first time.

Reuse SQLStatement Instances

Rather than creating a new SQLStatement object for each query you want to run, it is recommended that you reuse an existing instance, and just alter the parameters associated with the object. You will reap three benefits from doing this:

  • The query will be cached, so the SQLite connection will not have to compile a new statement every time you want to run the query
  • You will get automatic type conversion when assigning values like strings, integers, dates and so on
  • You will improve data integrity within the database because as the automatic type conversion also acts as a kind of filter

For queries that get used across an entire application, I have found that putting the SQLConnection object and wrapper functions for the SQLStatement objects inside a singleton class seems to work very well.

NOTE: Every time you change the text property, the query will get recompiled. The trick is in using the parameters.

The following example demonstrates this:

package
{
	import flash.data.SQLConnection;
	import flash.data.SQLStatement;

	public class SQLConnectionWrapper
	{
		private static const SINGLETON_INSTANCE:SQLConnectionWrapper = new SQLConnectionWrapper(SingletonLock);

		public var connection:SQLConnection;

		private var selectRecord:SQLStatement;

		public static function get instance():SQLConnectionWrapper
		{
			return SINGLETON_INSTANCE;
		}

		public function SQLConnectionWrapper(lock:Class)
		{
			// This ensures that only once instance of this class may be created and accessed
			if(lock != SingletonLock){
				throw new Error("Class Cannot Be Instantiated: Use SQLConnectionWrapper.instance");
			}

			createDatabase();
		}

		private function createDatabase():void
		{
			// This creates an SQLConnection object , which can be accessed publicly so that event listeners can be defined for it
			connection = new SQLConnection();

			var databaseFile:File = File.applicationStorageDirectory.resolvePath("database.db");
			connection.openAsync(databaseFile);
		}

		public function getRecord(recordId:int):SQLStatement
		{
			// If selectRecord has not been instantiated, then create the instance with all the data that it needs
			// If it has been instantiated, then we can skip over this part and take advantage of the fact that it has now been cached
			if(!(selectRecord is SQLStatement)){
				selectRecord= new SQLStatement();
				selectRecord.sqlConnection = connection;
				selectRecord.text =
				"SELECT record_id, description, is_active " +
				"FROM record_tbl " +
				"WHERE record_id = :recordId";
			}
			// This simply changes the one parameter that needs to be changed
			// Because recordId has already been declared as an int, this will be converted into an SQLite recognized integer
			selectRecord.parameters[":recordId"] = recordId;

			return selectRecord;
		}
	}
}

// This little class exists to stop extra instances on the singleton being created
class SingletonLock{}

Use Responders

One major problem that caught me off guard when trying to reuse instances was event handling. If I were to use SQLConnectionWrapper.instance.getRecord() in multiple places across my application, and attach event listeners to them, I will encounter a problem where all three event listeners get triggered. Even when each event listener is removed, errors still occur due to the fact that the SQLStatement object still thinks that it is executing. Not pretty.

Using Responder objects allows to avoid the pain that come s from constantly adding and removing event listeners, and prevent the race condition that occurs with the SQLStatement objects. The Responder object allows you to define event handlers for both SQLEvent.RESULT and SQLErrorEvent.ERROR events. And rather than attaching itself to the SQLStatement object, it will only handle events generated for each particular call to SQLStatement.execute().

NOTE: After a successful query, the handler will be passed an SQLResult object as the first argument, and not an SQLEvent object.

NOTE: After a failed query, the handler will be passed an SQLError object as the first argument, and not an SQLErrorEvent object.

Here is an example:

package
{
	import SQLConnectionWrapper;

	import flash.net.Responder;
	import flash.data.SQLResult;

	public class SampleApplication
	{
		// Create the responder object once, so that it can be reused again and again
		private var responder:Responder = new Responder(handleSuccess, handleFailure);

		public function SampleApplication():void
		{
			// This opens the database - obviously
			SQLConnectionWrapper.instance.connection.addEventListener(SQLEvent.OPEN, executeQuery);
			SQLConnectionWrapper.instance.connection.open();
		}

		private function executeQuery():void
		{
			// This statement essentially creates a reference to the SQLStatement object that is created once for all time within the SQLConnectionWrapper singleton
			var statement:SQLStatement = SQLConnectionWrapper.instance.getRecord(1);
			statement.execute(-1, responder);
		}

		private function handleSuccess(result:SQLResult):void
		{
			trace("Record ID: " + result.data[0].record_id + ", Description: " + result.data[0].description + ", Is Active: " + result.data[0].is_active);
		}

		private function handleFailure(error:SQLError):void
		{
			trace("Epic Fail: " + error.message);
		}
	}
}

Boolean Objects

When returning results from a SELECT query, any SQLite Boolean objects will be correctly converted into ActionScript Boolean objects. However, when executing an INSERT or UPDATE query, an integer is actually required. This quick and nasty workaround demonstrates what has to take place:

This would need to be part of SQLConnectionWrapper

		private function setRecord(description:String, isActive:Boolean):void
		{
			if(!(insertRecord is SQLStatement)){
				insertRecord = new SQLStatement();
				insertRecord.sqlConnection = connection;
				insertRecord.text = "INSERT INTO record_tbl (description, is_active) VALUES (:description, :isActive)";
			}
			insertRecord.parameters[":description"] = description;
			// This is pretty straightforward, but it simply converts the ActionScript Boolean into an int, so that SQLite can accurately create it's own boolean in the database
			insertRecord.parameters[":isActive"] = isActive ? 1 : 0;

			return insertRecord;
		}

Date Objects

When executing an INSERT or UPDATE query, dates will be inserted into the database as expected (well, kind of – if you really want to know more about that then just ask about it). In the case of Date objects, though, the troubles occur when trying to return Date objects from a SELECT query. I won’t try and explain this one, because there is already a great explanation of what is happening here.

What I will do is provide some code that demonstrates the quickest way of getting a usable Date object from an SQLite database:

NOTE: The String object that gets returned is formatted so that it can be passed to the constructor of a Date object without having to jump through any additional hoops.

This would need to be part of SQLConnectionWrapper

		public function getAllBookings():SQLStatement
		{
			if(!(selectBookings is SQLStatement)){
				selectBookings = new SQLStatement();
				selectBookings.sqlConnection = connection;
				// This statement will return the date as a MM/DD/YYYY formatted string
				selectBookings.text =
				"SELECT booking_id, record_id, STRFTIME('%m/%d/%Y', checkout_date) AS checkout_date, STRFTIME('%m/%d/%Y', checkin_date) AS checkin_date " +
				"FROM booking_tbl";
			}

			return selectBookings;
		}

UPDATE: Paul Robertson has posted an update about this. Apparently setting the column type to DATE, rather than DATETIME, will allow you to use Flex Date objects natively. You can see this alluded to in the LiveDocs.

Blobs and Bitmaps

Do you want to store images in your database? Do you want to pull them out as DisplayObjects, ready to do with as you wish? Well, here is some code that demonstrates the entire process of loading an image from a file, storing it in your SQLite database, retrieving the image data, and converting it back into a DisplayObject:

NOTE: ByteArray objects do not need any special attention when being either SELECTed from or INSERTed into the database.

This would need to be part of the application code

		import flash.display.Bitmap;
		import flash.display.Loader;
		import flash.filesystem.File;
		import flash.net.URLLoader;
		import flash.utils.ByteArray;
		import mx.graphics.codec.PNGEncoder;

		private function selectPicture():void
		{
			// This little section here creates a file object, and then launches the file browser so that you can select your image
			var file:File = File.documentsDirectory;
			file.addEventListener(Event.SELECT, handleSelectPicture);
			file.browseForOpen("Select Picture");
		}

		private function handleSelectPicture(event:Event):void
		{
			// Once the image file has been selected, we now have to load it
			var loader:Loader = new Loader();
			loader.contentLoaderInfo.addEventListener(Event.COMPLETE, handleLoadPicture);
			loader.load(new URLRequest(event.target.url));
		}

		private function handleLoadPicture(event:Event):void
		{
			// The first thing that we do is create a Loader object (which is a subclass od DisplayObject)
			var loader:Loader = Loader(event.target.loader);
			// Next, we cast the loader as a Bitmpa object, as the Bitmap object has function to return a BitmapData object based on the image
			var image:Bitmap = Bitmap(loader.content);
			var encoder:PNGEncoder = new PNGEncoder();
			// The PNGEncoder allows you to convert BitmapData object into a ByteArray, ready for storage in an SQLite blob field
			var byteArray:ByteArray = encoder.encode(image.bitmapData);

			var statement:SQLStatement = SQLConnectionWrapper.instance.setPicture(1, byteArray);
			statement.execute(-1, responder);
		}

This would need to be part of SQLConnectionWrapper

		private function setPicture(recordId:String, byteArray:ByteArray):void
		{
			if(!(insertRecord is SQLStatement)){
				insertRecord = new SQLStatement();
				insertRecord.sqlConnection = connection;
				insertRecord.text = "INSERT INTO picture_tbl (record_id, data) VALUES (:recordId, :byteArray)";
			}
			insertRecord.parameters[":recordId"] = recordId;
			// The ByteArray should be added as a parameter; this makes the whole process of storing the image in the blob field very easy
			insertRecord.parameters[":byteArray"] = byteArray;

			return insertRecord;
		}

This would need to be part of the application code

		import mx.controls.Image;

		// This function would be defined in a Responder object that handles a successful query of picture_tbl
		private function handleSuccess(result:SQLResult):void
		{
			var image:Image = new Image();
			image.addEventListener(Event.COMPLETE, handleLoadPicture);
			image.load(result.data[0].picture);
		}

		private function handleLoadPicture(event:Event):void
		{
			var picture:DisplayObject = DisplayObject(event.target.content);
		}

Sorry, I was started to get a bit tired of writing towards the end of this article :P If you want me to elaborate on anything, just leave a comment. Hopefully the examples are enough to get you up and running with SQLite, whilst avoiding most of the gotchas that come along with it :)

March
19
2009

I’m surprised to hear more about your problems with the Boolean data type. I’ve used it in INSERT statements without any trouble, as long as I’m using statement parameters of course.

I’ve also used Date objects without any trouble. I posted a reply on the VerySimple.com post you link to, describing what I think is the issue.

P.S. Perhaps you know this already, but it’s extremely difficult to post a comment on this site. In your comment form, the foreground and background colors are the same, so when I type text in the comment box I can’t see it unless I select it.

March
19
2009

Hey thanks for the comment Paul. I just went through and did some testing with the Boolean data type, and it appears that you are correct – no issues here. It must have been quirk that came up during my initial testing – for some reason I thought it was inserting strings into the database.

I also look forward to seeing your post on VerySimple.com – it would be very useful to get a native Date object back from the SQLite. I suspect it may be due to the fact that I am using an SQLite Date field rather than a Datetime or Timestamp – if the post doesn’t show up soon, be sure to let me know what you have to get it working successfully.

Also thanks for the heads up on the blog, its all fixed now :)

April
9
2009
by Alex Lopez

Could you please provide a working source code of a succesfull browsing,inserting & displaying images example. I would appreciate it, i’ve been like crazy all over the net searching for a solution. Thanks in advanced.

May
19
2009

Hi,

I like your solution to the problem of using SQLite in Flex. Have you ever stored sounds in your database? I’m suprised it is such big problem to store sound blobs.

Chris

May
19
2009

Hey Chris, no, I have never used SQLite for storing sounds, although it shouldn’t be too hard from what I can tell. The Sound.extract() function will allow you to get a ByteArray of the sound, which is exactly what the encoder.encode(image.bitmapData) line does in the picture sample.

In order to read the sound from the database, I think it will be a different story. My guess would be to write the ByteArray to a FileStream using FileStream.writeBytes(), save the stream as a temporary file, then load the file stream using URLRequest. Although I am sure there is a more elegant way of doing this using an in memory file reference…

June
7
2009
by Alex

Hi Andrew, i’m interesting in your example for Blobs and Bitmaps, i’m a novice with Adobe Air…

June
8
2009
by Chiranjeevi

Hi Andrew
Nice explanation… Just I planned to do some thing similar to the Microsoft Thumbs.db approach as a part of my Adobe AIR application, and just tried to find out how to store the bitmap data into an SQL DB. And thus got this of your simple explanation… Nice to be read. I am going to Implement it. I like the way you did.

thanks A lot.

June
18
2009
by Jubal Early

I had been having troubles with this (Bitmap->BitmapData->ByteArray->db and back).

The way I got it working was to save the image to the db for the most part as you did, except for base64 encoding it before saving (not sure way, guessing has to do with where somewhere along the way ByteArray is converted to an actionscript String, though not sure on String charset). On retrieving, base64 decode to ByteArray, then use Loader.loadBytes to load the image.

Works for me anyways.

If anyone can figure why I had to encode to base64 (or maybe why I don’t need to), and answer would be much appreciated.

June
19
2009
by Jubal Early

Ok, nevermind. Apparently BLOB type does not correspond to BLOB column affinity. Fixed by setting the data column where the ByteArray is bound to to NONE type (or blank).

July
1
2009
February
23
2010
by JP

This was incredibly helpful! Thank you so much for posting this.

March
31
2010
by Al

Hi

Love the tut about blobs and bitmaps, but am not sure how to get this actually running

Please could you explain it more because I am a dummy when it comes to image 2 bytearray stuff and I get lost the moment I read to code.

Pls help :)

Thanks

April
3
2010
by Danyul

Louis says thanks now that I’m able to make sense of this!

lol!!!1one!

December
24
2010
by Olivier Mungo

Great article, thanks. Helped me a lot.

February
23
2011
by Aessam

This is actually very nice ,thank u.

April
30
2011

Hi there,
Serious SqlLite newbie here. I’m having a few different problems, one of which is retrieving data from the database you’re using. From what I can tell you never set the tables up. I wonder if you could perhaps let me know how you do this?
Thanks in advance,
James

Leave a Response

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>