Adobe Air/Flex + SQLite database problem - apache-flex

I'm still a newbie to Adobe Air/Flex, and still fairly new with SQL.
I've downloaded this (http://coenraets.org/blog/2008/11/using-the-sqlite-database-access-api-in-air…-part-1/) code and have been looking over it and I'm trying to implement the same idea.
I think it's just something stupid. I'm using Flex Builder. I made a new desktop application project, didn't import anything.
I added a DataGrid object and bound it to an ArrayCollection:
I'm trying to make it so when the program initializes it will load data from a database if it exists, otherwise it'll create a new one.
The problem is, when the application runs, the datagrid is empty. No column headers, no data, nothing. I've tried changing a whole bunch of stuff, I've used the debugger to make sure all the functions are being called like they're supposed to. I don't know what I'm doing wrong. I've compared my code to the before mentioned code, I've looked for tutorials on Google. Anyone know what I'm doing wrong?
<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" width="672" height="446"
applicationComplete="onFormLoaded()"
title="iRecipes">
<mx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
private var sqlConnection:SQLConnection;
[Bindable] private var recipeList:ArrayCollection;
private function onFormLoaded():void
{
sqlConnection = new SQLConnection();
openDataBase();
}
private function openDataBase():void
{
var file:File = File.userDirectory.resolvePath("recipes.db");
sqlConnection.open(file, SQLMode.CREATE);
if(!file.exists)
{
createDatabase();
}
populateRecipeList()
}
private function createDatabase():void
{
var statement:SQLStatement = new SQLStatement();
statement.sqlConnection = sqlConnection;
statement.text = "CREATE TABLE Recipes (recipeId INTEGER PRIMARY KEY AUTOINCREMENT, recipeName TEXT, authorName TEXT)";
statement.execute();
statement.text = "INSERT INTO Recipes (recipeName, authorName) VALUES (:recipeName, :authorName)";
statement.parameters[":recipeName"] = "Soup";
statement.parameters[":authorName"] = "Joel Johnson";
statement.execute();
statement.parameters[":recipeName"] = "Garbage";
statement.parameters[":authorName"] = "Bob Vila";
statement.execute();
}
private function populateRecipeList():void
{
var statement:SQLStatement = new SQLStatement();
statement.sqlConnection = sqlConnection;
statement.text = "SELECT * FROM Recipes";
statement.execute();
recipeList = new ArrayCollection(statement.getResult().data);
}
]]>
</mx:Script>
<mx:DataGrid dataProvider="{recipeList}">
</mx:DataGrid>
</mx:WindowedApplication>

I just tried this out using your code. I made a change and removed the condition as I was getting errors about the table not existing.
//if(!file.exists)
//{
createDatabase();
//}
This got the datagrid showing the correct info. I think that there is something wrong with the way you are initialising the database file. I'm having a look into it at the moment.
Try using
sqlConnection.open(file, SQLMode.CREATE);
instead, for opening the database.

Thanks Feet. With your suggestion, I believe I have figured it out. I changed the if statement to this:
if(!file.exists)
{
sqlConnection.open(file, SQLMode.CREATE);
createDatabase();
}
else
{
sqlConnection.open(file, SQLMode.UPDATE);
}
And it works great. Thanks for your help.

Related

Search function throwing error #1009 - null object reference

Simple to understand problem, hoping for similarly easy solution:
When I type in my search query, I sometimes get the "Error #1009 Cannot access a property or method of a null object reference" on the dataField = new ArrayCollection(result.data); line.
AS3:
private function getSearch():void
{
//status = "Loading data";
selectStmt = new SQLStatement();
selectStmt.sqlConnection = conn;
var sql:String = "SELECT [Index], Title, CAST(Picture AS ByteArray) AS Picture FROM Data WHERE Title LIKE #searchTarget";
selectStmt.parameters["#searchTarget"] = "%" + searchTarget + "%";
selectStmt.text = sql;
selectStmt.addEventListener(SQLEvent.RESULT, selectResult2);
selectStmt.addEventListener(SQLErrorEvent.ERROR, selectError);
selectStmt.execute();
targetRecordId = pngIndex;
}
private function selectResult2(event:SQLEvent):void
{
//status = "Data loaded";
selectStmt.removeEventListener(SQLEvent.RESULT, selectResult);
selectStmt.removeEventListener(SQLErrorEvent.ERROR, selectError);
var result:SQLResult = selectStmt.getResult();
dataField = new ArrayCollection(result.data);
if (result.data != null) {
pngIndex = result.data.Index;
pngTitle = result.data.Title;
pngByteArray = result.data.Picture;
targetRecordId = pngIndex;
}
}
mxml:
<s:List id="myList"
x="0" y="40"
width="100%" height="100%"
labelField="Title"
dataProvider="{dataField}"
change="myList_changeHandler(event)"
>
</s:List>
Things I've tried (including the permutations of these solutions):
1) Moving the error code inside the SelectResult2 method
2) Adding an if (result.data == null) method
3) Using Array instead of ArrayCollection (I found someone who found that this worked for their project on some forum somewhere)
4) Adding a timer function to try and limit how often the db will get searched. (although I think this was the best solution, I think I will have to try this one again)
Note, the error occurs, from what I can tell, largely as a result of entering search characters too quickly
Thank you for your help.
1. Use Responders
You're using the same event handler function for each call to the database. Hence when the function gets executed it might have lost the reference to its original statement (because a new call was initiated in the meantime). That's why you get those nullpointers on the result object.
So ditch the event listeners and use a Responder instead, like so:
var stmt:SQLStatement = new SQLStatement();
stmt.sqlConnection = connection;
stmt.text = query;
var resp:Responder = new Responder(onResult, onFail);
stmt.execute(-1, resp);
The 'onResult' function will take a SQLResult object as a single argument and you no laonger have to reference the original statement to get that result.
2. Delay
Use a Timer, yes. But don't use it to make the database call on an interval. You wouldn't know when to stop making those calls, would you? Use it to detect whether the user is still typing or not: when it takes longer than - say - 300ms. for one KeyboardEvent.KEY_UP to come after another, then fire the call to the database. Once.
3. Use a threshold
Don't start querying with just one letter. Use a threshold of at least 2-3 characters. You won't get any relevant suggestions with one character anyway. How high the threshold should be, depends on the size of the collection to search.
4. Filter an ArrayCollection
Ignore all of the above. An easier solution might be to just load all the records you want to search into an ArrayCollection and use filterFunction to show only the records that match a certain String. This method will do fine as long as the collection is not gigantic (like tens of thousands of records) and is way faster to implement.
first, how about
if(result == null)
return;
Also, the timer is a good idea, usually about half a second, that way your database isn't being hammered while the user is typing.
private var timer:Timer = new Timer(500, 1);
protected function textChangeHandler():void{
timer.reset();
timer.addEventListener(TimerEvent.Timer, getSearch);//Could be moved to a creation complete
timer.start();
}
error handle and remove/add events as appropriate. You could also disable the search until the last one has completed, which in combination with the timer shouldn't be intrusive to the user.

flex fusion charts with webservice

I have flex project where i want to call webservice and pass values from it to the xml file to update fusion chart xml
My code is
<fx:Script>
<![CDATA[
import mx.controls.Alert;
import mx.events.FlexEvent;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
public var PUENumber:String;
protected var xmlLoader:URLLoader;
[Bindable]
public var avgPUEXml:XML = new XML;
protected function init():void
{
xmlLoader = new URLLoader();
xmlLoader.addEventListener(Event.COMPLETE,setDataXML)
xmlLoader.load(new URLRequest("data/AvgPUE.xml")); //Loading xml file for the chart from the folder
};
protected function setDataXML(event:Event):void
{
avgPUEXml = XML(event.target.data);
avgPUEXml.value = PUENumber; //updating chart xml value
fw.FCDataXML = avgPUEXml.toString();
fw.FCRender();
};
protected function getDC_POWERdataResult_resultHandler(event:ResultEvent):void
{
PUENumber = getDC_POWERdataResult.lastResult; //getting value to update in xml file
init();
}
protected function bordercontainer1_creationCompleteHandler(event:FlexEvent):void
{
getDC_POWERdataResult.token = mGEMWS.getDCPUE("4","715"); //user details to get data from the method
}
]]>
</fx:Script>
<fx:Declarations>
<s:CallResponder id="getDC_POWERdataResult" result="getDC_POWERdataResult_resultHandler(event)"/>
<mgemws:MGEMWS id="mGEMWS" showBusyCursor="true"
fault="Alert.show(event.fault.faultString + '\n' + event.fault.faultDetail)"/>
</fx:Declarations>
<ns1:FusionWidgets id="fw" FCChartType="Bulb" FCDataXML="avgPUEXml" />
in the declaration am calling web service. and on creationCompelete am sending userid details to get data from webservice. now where exactly i need to call init function so that it updates xml file with values coming from web service and display the fusion widget
You don't need the 'setDataXML' handler method. Also you don't need any 'URLLoader' or 'URLRequest' in the 'init' method. FusionCharts supports XML data as well as XML path. So remove the 'setDataXML' and remove all the codes from the 'init' method and add just fw.addEventListener(FCEvent.FCRenderEvent, wgdtRendHandler);
and add below handler method -
protected function wgdtRendHandler(e:FCEvent):void{
e.target.FCSetDataURL='data/AvgPUE.xml';
e.target.removeEventHandler(FCEvent.FCRenderEvent, wgdRendHandler);
}
I think this should work for you. In case you still don't get the updated data in the chart, check the XML file in the 'init' method whether it's updated properly at that point of time.
You can call the init function when the chart finishes loading and is ready for data.
You can do this listening to FCLoadEvent or FCRenderEvent of the gauge.
Please try using :
<ns1:FusionWidgets id="fw" FCChartType="Bulb" FCDataXML="avgPUEXml" FCRenderEvent="init()"/>
or
<ns1:FusionWidgets id="fw" FCChartType="Bulb" FCDataXML="avgPUEXml" FCLoadEvent="init()"/>

Load Content into TextInput Flex?

I've got content coming into my application using a query and an ArrayCollection. I know how to display the content into a DataGrid by using the dataProvider propriety, but I'd like to use TextInput components and drop the DataGrid altogether.
Does anyone have any examples or information on how I would go about doing this?
Thanks!
Thank you for the insight - invertedSpear
I'm still having a problem all that displays is [object,object]
Here is a bit of my code.
[Bindable]
private var acCon:ArrayCollection;
private function reData():void //RETRIEVE DATA
{
var stmt:SQLStatement = new SQLStatement();
stmt.sqlConnection = sqlConn;
stmt.text = "SELECT * FROM person";
stmt.execute();
var result:SQLResult = stmt.getResult();
acCon = new ArrayCollection(result.data);
}
<mx:Repeater id="repeater1" dataProvider="{acCon}">
<mx:Label id="Label1" text="{repeater1.currentItem}"/>
</mx:Repeater>
Any thoughts?
Your question is not clear. The format of your query result and exactly how you are wanting to display them make a difference in this.
Things you need to do no matter what the format.
1) make sure you have a bindable variable to store your query result in:
[Bindable] public var myArrayCollection:ArrayCollection = new ArrayCollection();
2) assign you query result to this.
3) probably going to need to use a loop or a repeater based on your results length, not sure what your result looks like so you will have to figure this out.
4) assign the values to text boxes.
<mx:Text text="{myArrayCollection.FieldName}">
This answer is my best guess to what your question is. Please edit your question to make it more clear if this answer doesn't work for you.
Looking at your code example that's now up I am guessing you are very close, your repeater is kind of like your query result. Current item is like a row of your query. you probably just need to add the field name to it. So Maybe:
<mx:Label id="Label1" text="{repeater1.currentItem.LastName}"/>

HTTPService Result - checking number of items with a specified name

I have a question about HTTPService and the data it returns.
Well lets consider this XML:
<PhotoGalleryData>
<Photo>
<id>1</id>
<name>Summer Vacation</name>
<description>In vacation</description>
<fullImage>originalImg/1.JPG</fullImage>
</Photo>
<Photo>
<id>2</id>
<name>Winter Vacation</name>
<description>coold</description>
<fullImage>originalImg/2.JPG</fullImage>
</Photo>
</PhotoGalleryData>
As you see i have two instances of Photo, that would be retrieved using a HTTPService, well then on the Result Event of that same HTTPService i would want him the count the amount of instances named Photo he as returned on is .lastResult.
This is a dumb question, but i can't find it anywhere in Adobe Docs.
Of course any help, hint, suggestion is greatly appreciated.
Medoix
I gotta be blind or something, because it still returns 0.
Something missing here?
MXML
<mx:HTTPService id="getData"
url="{XMLDataFileLocation}"
showBusyCursor="true"
fault="getDataFaultHandler()"
result="getDataResultHandler(event)"/>
ActionScript
import mx.controls.Alert;
import mx.rpc.events.ResultEvent;
private var xmlData:XMLList;
private var numItems:int;
private function getDataResultHandler(evt:ResultEvent):void
{
if (evt.result.PhotoGalleryData)
{
xmlData = XML(evt.result).descendants("Photo");
numItems = xmlData.length();
Alert.show('Nº '+numItems,'num de Photo');
}
}
in the http_result function you have you will be putting this data in an XMLList for an example and then you can call the xmllist.length();
private var xmlData:XMLList;
private var numItems:Integer;
private function HttpResult(evt:ResultEvent):void {
if (evt.result.PhotoGalleryData) {
xmlData = XML(evt.result).descendants("Photo");
numItems = xmlData.length();
}
}
EDIT: Do the below...
Change
<mx:HTTPService id="getData"
url="{XMLDataFileLocation}"
showBusyCursor="true"
fault="getDataFaultHandler()"
result="getDataResultHandler(event)"/>
To...
<mx:HTTPService id="getData"
url="{XMLDataFileLocation}"
resultFormat="e4x";
showBusyCursor="true"
fault="getDataFaultHandler()"
result="getDataResultHandler(event)"/>
This is working for me.
Just do the following. it will solve your probs ;)
private var xmlData:XMLList;
private var numItems:Integer;
private function HttpResult(evt:ResultEvent):void {
if (evt.result.PhotoGalleryData) {
numItems = ArrayCollection(evt.result.PhotoGalleryData.Photo).length;
}
}
RSTanvir

adobe flex problem with Base64Encoder

in the following code :
var benq:Base64Encoder = new Base64Encoder();
benq.encode("force",0,5);
var tmp:String = benq.toString();
'tmp' turns out to be an empty string, i.e. with length 0.
why?
how to encode a string using base64encoder?
Are you sure that your code isn't working.
I just copied and pasted it into a test app and it returned tmp as 'Zm9yY2U='
Are you doing anything else to the var tmp? if debugging make sure that it has processed the var tmp:String.... line when your checking the output
<?xml version="1.0" encoding="utf-8"?><mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
layout="vertical"
verticalAlign="middle"
backgroundColor="white"
creationComplete="but1.label = encodeit()">
<mx:Script>
<![CDATA[
import mx.utils.Base64Encoder;
private function encodeit(): String {
var benq:Base64Encoder = new Base64Encoder();
benq.encode("force",0,5);
var tmp:String = benq.toString();
return tmp;
}
]]>
</mx:Script>
<mx:Button
id="but1"
width="100"
height="100"
/></mx:Application>
Are you using Flex 3, as it seems to be a new feature? Also try encoding into a bytearray using encodeBytes and using encodeUTFBytes, perhaps these work better.
Online reference is available from Adobe, but I guess you know that.
Ok, it is working.
The code that I posted was different from what I was actually using.
I skipped over the fact that calling toString() for Base64Encoder
clears its internal buffer. So, calling it the next time would return
an empty string.
Sorry for the trouble.

Resources