May 28, 2008

QuickConnect iPhone: an iPhone UIWebView hybrid framework

Posted in iPhone development tagged , , , , , , , , , , , at 5:31 pm by tetontech

Having seen the interest in the UIWebView post I made a while back, a followup posting on the framework that includes the class discussed in that posting seems to be needed. I created this framework so that you don’t have to reinvent the wheel when you start creating iPhone hybrid applications. An Objective-C version of this framework is also in the works. I look forward to receiving feedback from you.

A new version of QuickConnectiPhone is now available.  To see the new functionality that is in the current shipping versions look here
For a QuickStart on how to use QuickConnect look here

This posting is just a brief overview of the framework to give you an idea of how you can use it. The download of the framework, select the iPhone download out of the family, includes a simple example application. The framework is open source and is licensed under lgpl so you can use it in your free application or one you decide to charge for.

The QuickConnect iPhone framework is designed to make iPHone hybrid applications easy to create and yet use as little processing power and memory as possible. The framework is distributed as Xcode and Dashcode projects that you can drop into each of those applications. When you do they become available in the gallery of project types.
Selecting a new Dashcode Mobile QuickConnect projectSelecting a new Xcode Mobile QuickConnect project
These two new project types allow you to create and test your application from within Dashcode and then move it into Xcode, compile it, run it, and install it directly onto your device. No internet connection is then required to use your application.

The framework includes a javascript wrapper to the SQLite database that is embedded in WebKit, the engine used by the UIWebView class. This wrapper, called a Data Access Object (DAO), allows you to make calls to the database using two simple methods, getData and setData. You use getData to do queries and setData to do updates, insertions, and other modifications to the database. The result of a query is returned to you as a standard JavaScript array and other information is stored in the DAO such as any error numbers, error messages, the field names and their indices, etc. If you have inserted a record into a table and the table has an auto incrementing primary key field the DAO also stores the newly created key value. All of this is done without you needing to know the peculiarities of interacting with SQLite. For example,in WebKit all of the actual API calls to the database are asynchronous. You don’t need to worry about this since the framework and the DAO take care of this for you.

It must be noted that beta 5 of the iPhone OS 2.0 does not yet have the capability to interact with the SQL database from within JavaScript. It does work from within the simulators accessible from Xcode and Dashcode. A defect has been reported to Apple and I am hoping the functionality will be included in an upcoming beta release.

If you choose to retrieve remote data from a web server the QuickConnect iPhone framework supplies you with a Server Access Object (SAO). This SAO behaves like the DAO but wraps AJAX calls instead of database calls. You use it the same way as the DAO. It has a getData function that uses a GET type of request and a setData that is a POST type of request.

Since your application is running as a hybrid application you can use the SAO to make calls to any number of servers in any number of locations in your application. This is different than if you were making an AJAX call from within a browser. In that case you can only request data from the server that the current page originated from. Since you can now request data from any server you wish you will need to make sure the data you received is secure. The framework supplies you with security functions to make this easy for you.

The QuickConnect iPhone framework allows you to create what are called control functions. The types of these control functions are:

  • Validation Control Functions (ValCF’s) – You use these to validate user input. You can have zero, one or more ValCF’s for each user interaction with your application.
  • Business Control Functions(BCF’s) – This is where you make a call the Data Access Objects’ (DAO) methods to query or modify the database. There is one of these per user interaction.
  • View Control Functions (VCF’s) – These are where the screen is updated for the user and other ‘view’ type functionality is defined. For example you may want to update some number of global variables or the client-side Session object supplied by the framework. You can have one or more VCF’s per user interaction.
  • Error Control Functions(ECF’s) – Zero, one, or more of these functions can be applied to a command. This is where error handling is done. These errors may be due to user interaction or, if you choose to retrieve data from a web server, from a web server.
  • Security Control Functions(SCF’s) – Zero, one, or more of these functions can be applied to a request to a web server to ensure that the data received has not been interfered with via something like a man-in-the-middle attack. The example application doesn’t access a remote server so there are none created or mapped.

The framework uses a Command/Behavior paradigm. This allows you to create each of these type of functions in the provided functions.js file, or any other file you wish, and then place a call to the mapping function that will map your command to the control function. Below are the example mappings and control functions from the example application included in the QuickConnect iPhone download. This is just a partial example. To see the full example please download the framework.

/*
*
* This file is organized by the command being handled.
*/

mapCommandToValidation("setName",validateNameInputValCF);
mapCommandToBCF('setName', setNameBCF);
mapCommandToView('setName', displayNameVCF);
mapErrorToControl('setName', dbError);

mapCommandToBCF('getName', getNameBCF);
mapCommandToView('getName', displayNameVCF);
mapErrorToControl('getName', dbError);

 

mapCommandToBCF('clearName', dropTableBCF);
mapCommandToView('clearName', displayPromptVCF);
mapErrorToControl('clearName', dbError);


/*
* This validation function ensures that the name field is not empty
*/
function validateNameInputValCF(){
if(document.getElementById('nameInput') != null){
//retrieve the name and remove any white space
//from the beginning and end of the field values if any
var name = trim(document.getElementById('nameInput').value);
if(name != ''){
return true;
}
}
return false;
}

/*
*
*
* Business Control Functions
*
*
*/

function setNameBCF(){

//retrieve the welcome name and remove any white space
//from the beginning and end of the fields' values
var userName = trim(document.getElementById('nameInput').value);
var params = [userName];
//insert the name into the database
database.setData('INSERT OR REPLACE INTO names (id, name) values( 1, ?)', params);
}

function getNameBCF(){
//document.getElementById('displayName').innerHTML = database.getData;
//document.getElementById('nameDisplay').innerHTML = 'getting Name '+window['openDatabase'];
database.getData('SELECT name FROM names WHERE id=1');
//return nothing since database query results are handled asynchronously
}

function dropTableBCF(){
database.setData("DROP TABLE names");
}

/*
*
*
* View Control Functions
*
*
*/

/*
* This function displays a name if one was found in the database or
* if a name was just inserted into the database.
*/
function displayNameVCF(result){
var aName = "";
var data = result.getData();
//if the result has data then this data must have been retrieved from the database
if(data){
if(data.length > 0){
aName = data[0][0];
}
//no name was found in the database
else{
displayPromptVCF(data);
return;
}
}
//there must have been an update done
else{
aName = trim(document.getElementById('nameInput').value);
}
document.getElementById('nameDisplay').innerHTML = aName;
document.getElementById('nameInput').value = "";
}
/*
* This function displays to the user that they need to enter a name for display since no
* stored value could be found.
*/
function displayPromptVCF(data){
aName = "Please enter a name to be displayed.";
document.getElementById('nameDisplay').innerHTML = aName;
document.getElementById('nameInput').value = "";
}

/*
*
*
* Security Control Functions
*
*
*/

//Since no there is no accessing of data from servers there are no security control functions in this example

/*
*
*
* Error Control Functions
*
*/

 

function dbError(){
displayPromptVCF();
}

To see the full example and how the framework is embedded in an Objective-C application and what the other parts of the framework are please download the framework.

24 Comments »

  1. dave said,

    I have been playing with this, looks interesting and I will certainly be following where it goes.

    One thing I noticed: QCUtilities.js has function location(longitude,latitude,altitude) { … } which seems to break things for people who expect to be able to use window.location.href and the like.

  2. tetontech said,

    Thanks. I’ll change the name of the function to GPSLocation in the next beta.

  3. Andy Fuchs said,

    Hi Lee,

    I posted a comment elsewhere (but can’t find it anymore 😦 ).

    I can’t seem to find a download including a working database example (at least nothing worked in the iPhone simulator). I did, however, get it to work somehow in Safari (or Webkit).

    Could it be it just doesn’t work in Dashcode?

    Thanks

    andy

  4. tetontech said,

    Andy,

    It is working in Dashcode on my machine and on other people’s machines. What does your database definition file look like?

  5. Andy Fuchs said,

    Hi Lee,

    thanks for getting back. I am not sure, if I miss something important, but my code runs (somewhat) in Safari/WebKit, but not in Dashcode. I can’t see how to get the data back from the database.

    I used your demo-code; something like:
    database = new PhoneDataAccessObject(“WelcomeExample”, “1.0”, “Welcome example”, 20);
    //these lines will create the table if it doesn’t exist.
    database.setData(“CREATE TABLE IF NOT EXISTS names (id INTEGER UNIQUE, name TEXT)”);

    var m = “INSERT INTO names (‘name’) VALUES ( ” + content + “)”;

    var rs = database.getData(“SELECT name FROM names”);

    rs remains undefined….

    Are there any pre-requisites necessary)?

    Or do you have a small (complete) example-application displaying some data from a database (which works in Dashcode and/or the iPhone simulator), so I can see what the difference is?

    thanks in advance

    andy

  6. Andy Fuchs said,

    Hi Lee,

    never mind… it was a really stupid error on my side:
    I referenced the DAO:

    database = new PhoneDataAccessObject

    But ‘database’ was a misnomer (the pitfalls of copy and Paste 😉 ), so later when I tried to issue a transaction it didn’t work:

    database.transaction(xx); //<- WRONG

    database.db.transaction(xx); // <-WORKS JUST FINE

    A small example application would not be bad, though 😉

    Thanks for listening

    Andy

  7. tetontech said,

    Andy,

    Can you share what you are doing with the transaction? The getData and setData methods are both transaction safe.

    If there is something they don’t allow that you are doing I would like to add it into the PhoneDataAccessObject.

    Lee

  8. Andy Fuchs said,

    Lee,

    I did nothing special. I was just testing the code flow and wanted to see the difference. Currently I just use a couple of INSERT and SELECT statements to get comfortable with QC.

    There are a couple of things I’d like to mention here:

    Nested Callbacks:
    —————–

    – I’d rather add a target callback to the getData function, where the query result is sent to. I personally find it more readable to keep functions separated instead of nesting. Something along these lines:

    function getNames() {
    database.getData(‘SELECT * FROM names’, [], false, _namesReceiver); // in your current implementation this would only work when called directly: ‘database.dbAccess(…)’
    }

    function _namesReceiver(tx,rs) {
    // handle resultSet
    }

    function getNames() {
    database.db.transaction(_exec_sqlCommand);
    }

    function _exec_sqlCommand(tx) {
    tx.executeSql(‘SELECT * FROM names’, [], _namesReceiver);
    }

    (Background: I often use dataContainers containing the databaseCursor as data object). That way data is processed when it’s needed/displayed, which gives a little more speed when handling lots of data.)

    Session vs. Preferences:
    ————————–

    Although I like your session idea, I use my own (virtual) Preferences class to have persistent data throughout the application runtime. In this preference class, there’s an output object which save this data either to a preference file or a database (or whatever). Also data is not written constantly after every write to Preferences class, but dumped to file in intervals to avoid lots of write commands. This avoids lots of read/writes and for smaller amounts of data it’s much faster than lots of reads/writes).

    Session -> Databases:
    ———————-
    I am not sure if I like mixing database functions into the Session class. I think I’d rather use a DAO Interface to decide to where I want my session data to get written.

    json2:
    ——
    In the past I had some problems using the json2 class, when it came to huge UTF8 XML data blobs. Therefore I switched to Prototype (1.6.0.2) and xml2json.js by Thomas Frank.

    mappings:
    ———
    I didn’t understand the advantage of using mappings (i.e. over listeners). I need to study this a little more (maybe you have a hint?)
    I usually keep data and accessors encapsulated in their own classes.

    Nested file structure:
    ———————-
    One thing I really don’t like is the fact, that all files are kept ‘flat’ in the target application bundle. Although this is not a big thing, it would be nicer to keep the web application’s directory structure. (And less work, when moving over to XCode 😉 )

    All in all QuickConnect is a really nifty piece of work! I will definitely get back to you and share what I have as soon as I am a little more comfortable with it.

    best

    andy

  9. tetontech said,

    Andy,

    Thanks for your comments. I’ll see if I can explain where I am coming from with the current code base.

    The idea is that someone can make calls to the database without having to know how SQLite works under the hood.

    To this end the SQLite result set object is never sent back to the user. Instead it is handled by the PhoneDataAccessObject and if a query is sent to the object a QueryResult object is generated.

    This result object contains a 2D JavaScript array containing the results of the query as well as other pieces of information.

    When the PhoneDataAccessObject is used with the rest of QC any ViewControlFunctions mapped to the same command as the BusinessControlFunction that called the PDAO will be called and passed the result object.

    Your statement “I’d rather add a target callback to the getData function, where the query result is sent to”

    These ViewControlFunctions are callback functions. You can have any number of them and they will be called in the order that you map them.

    Your statement “lthough I like your session idea, I use my own (virtual) Preferences class to have persistent data throughout the application runtime.”

    While the session object does persist the current state of the application during the applications execution, since the changes are also written to a database the state is also persisted between application runs if you choose to use it that way.

    This becomes important for iPhone applications since they could be interrupted by a phone call or by your application accessing another application on the phone such as the google map application.
    Imagine how irritating it would be for a user to be drilled down in your application, be sent to the google map or some other application, and then have to drill back down to where they were.
    I think that they would much prefer to have the app start back up where they left it when we sent them somewhere else.

    your comment “Also data is not written constantly after every write to Preferences class, but dumped to file in intervals to avoid lots of write commands. This avoids lots of read/writes and for smaller amounts of data it’s much faster than lots of reads/writes).”

    I agree 100%. I had hoped to be able to dump the session object out to the database at the calling of the unload event. Unfortunately since the SQLite javascript calls are asynchronouse there is no guarantee that they will complete prior to the closing of the engine. In fact they don’t and so nothing is persisted.

    While the session object could be persisted before we as programmers send the user to another app that is not the case when the phone kicks in and the app closes, such as when the user clicks on a phone number or receives and answers a call. Thus the only way to guarantee that any state items that you feel are important are stored into the database is to store each one as they are added to the session. You may want to think about this with the implementation you are using.

    your statement “I didn’t understand the advantage of using mappings (i.e. over listeners).”

    By using the application controller pattern, mapping commands to functions, you actually increase the flexibility of your code and at the same time if you group all of the mapping calls for a single command together you can read the mapping file to see the processing flow for handling a command.

    Since the commands are passed to the front controller first you have a mapping flow for doing validation for each command as well.

    Because of these mapping files it becomes much easier to see if you have missed a validation for any command and your validation isn’t scattered all over in your application.

    This all depends on we as programmers creating the Validation, Business, and View Control Functions correctly. They should be very small and they should never call each other.

    This makes them very modular and extremely re-usable. How many times should you have to write login validation script anyway?

    The idea for application creation is this.

    1 – create a business control function
    2 – create any required view control functions
    3 – create any required validation functions
    4 – map the business, view, and validation control functions to a command
    5 – call handleRequest from your code

    You now have the new functionality you wanted in your application. No glue code needed.

    your comment “One thing I really don’t like is the fact, that all files are kept ‘flat’ in the target application bundle. Although this is not a big thing, it would be nicer to keep the web application’s directory structure. ”

    I agree 100%. Unfortunately Xcode doesn’t use directories. It uses groups. If you look at an Xcode project you will see that all of the files end up in the same directory regardless of what the groupings used in the Xcode user interface are. Dashcode, as I’m sure you know, uses directories. Because of this there is a little time needed to make the resource files mesh up with what Xcode will do with the resource group.

  10. Nick said,

    Hi,

    great work! It’s just perfect for my recent project.
    Unfortunately i can’t get javascript working. All works fine, but scripting doesn’t seem to work, i.e. simple alert(‘xxx’). I’m testing on an iPod touch FW 2.0.2 (5C1).
    Is there any need to enable scripting for the UIWebView?
    Do you have any ideas?

    Kind regards
    Nick

  11. tetontech said,

    JavaScript is automatically turned on in the UIWebView. The alert() function is not actually a JavaScript function. It is a call out to the browser itself. Therefore it will not work in the UIWebView since it is not part of any of the web engines including the WebKit engine that is used by the UIWebView.

    Have you tried inserting text into a div to do your debugging?

    There is another posting on my blog about how to make calls to objective-c for doing debugging. I am close to releasing an update to QuickConnectiPhone that will have this built in.

  12. tetontech said,

    QuickConnectiPhone is now in release candiate 4 on source forge. It includes an installer.

  13. tetontech said,

    Version 1.1 is now available. It includes an installer for both Xcode and Dashcode templates. It also includes a beta version of QC for developing Mac applications. A version for developing PHP server applications is there as well.

    To see the new functionality available in version 1.1 look here. https://tetontech.wordpress.com/2008/12/30/quickconnectiphone-version-11-is-now-available/

    For a QuickStart on how to use QuickConnect look here. https://tetontech.wordpress.com/2008/12/30/a-quick-reference-for-how-to-create-quickconnectiphone-applications/

  14. Linda said,

    Are there any manual install instructions anywhere for QuickConnect for the iPhone? I have all of my developer folders on an external drive and they are not in the root directory of the external drive. When I try to install, I only have a choice of the root directories.

    Thanks,
    Linda

    • tetontech said,

      I haven’t done a manual install for some time. The standard install directory for the Xcode template is
      /Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Project Templates/Application/QuickConnect iPhone Application

      The standard install directory for the Dashcode template is
      /Developer/Applications/Dashcode.app/Contents/Plugins/

      The Xcode object template destination file is
      /Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/File Templates/Cocoa Touch Classes/QCCommandObject subclass.pbfiletemplate

      The Xcode JavaScript Auto-complete macros install in
      /Developer/Applications/Xcode.app/Contents/PlugIns/TextMacros.xctxtmacro/Contents/Resources/QuickConnect.xctxtmacro

      Were these directories and files created in the standard root directory? If they were move them into your installation. If not let me know and I’ll see how I can get you these files.

      Lee

  15. […] Oorspronkelijk geplaatst door Dreissen Hallo, Ik ben pas bezig met het maken van apps. Nu zoek ik een stukje code die iemand voor me wil maken. Het gaat erom dat er 2 pagina’s zijn waar je op kan klikken nieuws en inloggen als je op nieuws klikt moet er een UIWebvieuw komen maar ik weer niet hoe en bij inloggen ook die moeten dan linken naar een webpagina. Kan iemand helpen? Bedankt! Misschien ben je hier iets mee !!! QuickConnect iPhone: an iPhone UIWebView hybrid framework Teton Technical […]

  16. Linda said,

    I have all Mac developer tools installed on an external drive. When I try to install QuickConnect I get an error that it cannot be installed. Is there a way for me to manually install it to the directory of my choice or rather on the external drive rather than on the Mac itself?

    Thanks,
    Linda

  17. Linda said,

    Sorry, I see that you answered me previously. I missed that post.

    Linda

  18. Dodgy said,

    Im currently using the Quickconnect framework for an iphone project and have found what appears to be a bug. As I can’t find anywhere else to post this I’ll do it here, sorry if I’m off topic. While alerts rotate correctly, prompts don’t. So you end up with a sideways dialog and keyboard. Has anyone come across this problem and solved it.

    • tetontech said,

      The Google Group may be a more appropriate place for this question. Look to the bottom right for the link. Also, I’m not sure what you mean by ‘alerts’ and ‘prompts’. What is the code involved?

  19. shang said,

    If the latest version 1.5.1 of QuickConnect iPhone works with Xcode 3.1.3 and Dashcode 2.0.1 on OS X 10.5.8?

    I tried to create a very basic example or use some existing examples come with the installer, but I got lots of warnings when I compiled or ran them.

  20. tetontech said,

    Version 1.5.1 is built around Xcode 3.2. The Dashcode examples haven’t been updated to the latest version of Dashcode so they should still work.

    Try the 1.5 RC_4 version and see if that works.

  21. […] QuickConnect iPhone. This framework works very similar to PhoneGap. There are really no significant differences other than the approaches to hooking into the aforementioned embedded browser. […]


Leave a comment