Storing data directly in the browser

Một phần của tài liệu Manning android in action 3rd (Trang 478 - 487)

One of the historical challenges to web applications is the lack of locally stored data.

When performing a frequent lookup, it’s often too time- and bandwidth-intensive to con- stantly fetch reference rows from a server-side database. The availability of a local-to-the- browser SQL database brings new possibilities to web applications, mobile or otherwise.

Figure 16.4 Facebook mobile

Support for SQL databases varies across browsers and versions, but fortunately for us, the Android browser supports this functionality. Once again the WebKit engine relationship pays dividends as we demonstrate using the desktop version of the browser to debug our application. It’s mobile development, but that doesn’t mean you’re constrained to work- ing exclusively on the device! The sample application used in this portion of the chapter illustrates the basics of working with a locally stored SQL database.

16.3.1 Setting things up

The local SQL database accessible through the Android browser is essentially a wrap- per around SQLite. As such, any syntactical or data type questions can be satisfied by referring to the documentation for SQLite. To learn more about the underlying data- base technology, refer to the discussion in chapter 6 and or visit the SQLite website at http://sqlite.org.

For this application, we’re managing a single table of information with two col- umns. Each row represents a version of the Android operating system releases. A sim- ple web application is used to exercise the database functionality. Figure 16.5 shows the application screen when first loaded.

The sample application, which we present in the next section, is accessible on the web at http://android16.msi-wireless.com/db.php. Before diving into the code, let’s walk through the operation of the application.

Running the application is straightforward. The first thing to do is click the Setup button. This attempts to open the database. If the database doesn’t exist, it’s created.

Once the database is opened, you can add records one at a time by populating the two text fields and clicking the Save button. Figure 16.6 shows the process of adding a record.

Figure 16.5 The sample SQL application Figure 16.6 Saving a new record

The List Records button queries the database and displays the rows in a crudely formatted table. Figure 16.7 shows the result of our query after a single entry.

The final piece of demonstrable functionality is the option to remove all records. Clicking the Delete All Rows button opens the prompt shown in figure 16.8. If you confirm, the application proceeds to remove all the records from the database.

Remember, all of this is happening inside the browser without any interaction with the server side beyond the initial download of the page. In fact, there’s no database on the server! If 10 people all hit the site, download this page, and add records, they’ll be working independently with independently stored databases on their respective devices.

Let’s look at the code for this application.

16.3.2 Examining the code

Working with a SQL database within the browser envi- ronment involves the use of some nontrivial JavaScript.

If you’re not comfortable working in the flexible JavaScript3 language, the code may be difficult to fol- low at first. Stick with it—the language becomes easier as you let the code sink in over time. One helpful hint is to work with a text editor that highlights opening and closing braces and brackets. Unlike a compiled Android SDK application where the compiler points out coding problems during the development process, JavaScript errors are found at runtime. Anomalies occur, and you have to track down the offending areas of your code through an iterative process of cat and mouse.

Let’s begin by examining the UI elements of this application.

16.3.3 The user interface

We break down the code into two sections. The follow- ing listing contains the code for the UI of the applica- tion, stored in db.html.

3 For more on JavaScript, take a look at Secrets of the JavaScript Ninja at www.manning.com/resig. The book, by John Resig, is to be published by Manning in March 2012.

Figure 16.7 Listing the records from the table

Figure 16.8 Confirming deletion of records

<html>

<head>

<meta name="viewport" content="width=device-width" />

<script src="db.js" type="text/javascript" ></script>

</head>

<body>

<h1>Unlocking Android Second Edition</h1>

<h3>SQL database sample</h3>

<div id="outputarea"></div><br/>

1.&nbsp;<button onclick="setup();">Setup</button>

<br />

2.&nbsp;Add a record:<br/>

Version Number:&nbsp;<input id="id-field"

maxlength="50" style="width:50px" /><br/>

Version Name:&nbsp;<input id="name-field"

maxlength="50" style="width:150px" /><br/>

<button onClick="saveRecord(document.getElementById(

'id-field').value,document.getElementById(

'name-field').value);">Save</button>

<br/>

3.&nbsp;<button onclick="document.getElementById(

'outputarea').innerHTML = listRecords();">

List Records</button><br/>

<br />

4.&nbsp;<button onclick="if (confirm('Delete all rows. Are you sure?') ) {deleteAllRecords();}">Delete all rows</button>

<br />

</body>

</html>

The db.html file presents a simple GUI. This page runs equally well in either the Android browser or the desktop WebKit browser. It’s coded with mobile in mind, and as such includes the viewport meta tag B. All of the database interaction JavaScript is stored externally in a file named db.js. A script tag C includes that code in the page.

A div element with an ID of outputareaD is used for displaying information to the user. In a production-ready application, this area would be more granularly defined and styled. Clicking the Setup button E calls the JavaScript function named setup found in db.js. Ordinarily, this kind of setup or initialization function would be called from an HTML page’s onload handler. This step was left as an explicit operation to aid in bringing attention to all the moving pieces related to this code sample. We look more deeply at these JavaScript functions in the next section, so sit tight while we fin- ish up the GUI aspects.

Two text fields are used to gather information when adding a new record F. When

the Save button is clicked G, the saveRecord function is invoked. Listing the records is accomplished by clicking the List Records button H. Deleting all the records in the database is initiated by clicking the Delete All Records button I, which in turn invokes the deleteAllRecords function found in db.js.

Listing 16.5 User interface elements of the SQL sample page in db.html

B

Viewport meta tag

Reference JavaScript file

C

Output div

D

Call setup()

E

Gather required data

F

Save record

G

List all records

H

Delete I

all rows

With the basic GUI explanation behind us, let’s examine the functions found in db.js, which provide all of the heavy lifting for the application.

16.3.4 Opening the database

Now it’s time to jump into the db.js file to see how the interactions take place with the browser-based SQL database. The code for opening the database and creating the table is found in the following listing.

var db;

var entryCount;

var ret;

entryCount = 0;

function setup() { try {

db = window.openDatabase('ua2',1.0,'unlocking android 2E',1024);

db.transaction(function (tx) {

tx.executeSql("select count(*) as howmany from versions", [],

function(tx,result) {

entryCount = result.rows.item(0)['howmany'];

document.getElementById('outputarea').innerHTML = "# of rows : " + entryCount;

},

function(tx,error) {

alert("No database exists? Let's create it.");

createTable();

});});

} catch (e) {alert (e);}

}

function createTable() { try {

db.transaction(function (tx) {

tx.executeSql("create table versions(id TEXT,codename TEXT)", [],

function(tx,result) { },

function(tx,error) {

alert("Error attempting to create the database" + error.message);

});});

} catch (e) { alert (e); } }

All interactions with the database require a handle, or variable, representing an open database. In this code, the variable named dbB is defined and used for this purpose.

A variable named entryCountC is used to keep track of and display the number of records currently in the database. This variable isn’t essential to the operation of the code, but it’s a helpful tool during development. In the setup function, the variable db is initialized with a call to the openDatabase function D. The arguments to the

Listing 16.6 Code that opens the database and creates the table db handle

B

entryCount variable

C

D

Open database Execute SQL transaction

E

Result handler

F

Error handler

G

Handle error by creating table

H

I

Execute create table

openDatabase function include the name, version, description, and initial size alloca- tion of the database. If the database exists, a valid handle is returned. If the database isn’t yet in existence, it’s created and a handle returned to the newly created database.

Calling the transaction E method of the database object invokes a piece of SQL code.

The mechanics of the transaction method are nontrivial and are described in detail in section 16.3.5. For now, understand that the argument to the transaction method is a function that has four arguments: a SQL statement, parameters, a callback function for handling a successful invocation of the SQL statement, and an error- handling function invoked when an error occurs processing the SQL. The SQL state- ment invoked here attempts to get a count of the rows in the table named versions.

This value is stored in the entryCount variable F. If the table doesn’t exist, an error is thrown G. This is our cue to go ahead and create the table with a call to a user- supplied function named createTableH. The createTable function executes a sin- gle piece of SQL to create a table I. This method could be used to do any number of database initialization activities, such as creating multiple tables and populating each with default values.

Before we go through the balance of the transactions, it’s important to grasp how the transaction method of the database object is wired.

16.3.5 Unpacking the transaction function

All interactions with the database involve using the transaction method of the data- base object, so it’s important to understand how to interact with each of the four argu- ments introduced previously.

The first argument is a parameterized SQL statement. This is simply a string with any parameterized values replaced with a question mark (?). For example, consider a SQL statement that selectively deletes the iPhone from a table named smartphones:

delete from smartphones where devicename = ?

The second argument is an array of JavaScript objects, each element representing the corresponding parameter in the SQL statement. Keeping with our example, you need to provide the value needed for the where clause of the delete statement within the array:

['iPhone']

The net effect of these two lines together results in this SQL statement:

delete form smartphones where devicename = 'iPhone'

This approach keeps you from worrying about delimiters and reduces your exposure to SQL injection attacks, which are a concern when working with dynamically con- structed SQL statements.

The third argument is a function that’s invoked when the SQL statement is success- fully executed. The arguments to this callback function are a handle to the database transaction identifier along with an object representing the result of the statement.

For example, when you perform a select query against a database table, the rows are returned as part of the result, as you can see in listing 16.7, which shows the list- Records function from our sample application. In this listing, we use the returned rows to construct a rudimentary HTML table to dynamically populate the screen.

There are other ways of accomplishing this task, but we kept it simple because our pri- mary focus is on the interaction with the returned results set.

function listRecords() {

ret = "<table border='1'><tr><td>Id</td><td>Name</td></tr>";

try {

db.transaction(function(tx) {

tx.executeSql("select id,codename from versions", [],

function (tx,result) { try {

for (var i=0;i<result.rows.length;i++) { var row = result.rows.item(i);

ret += "<tr><td>" + row['id'] + "</td><td>" + row['codename'] + "</td></tr>";

}

ret += "</table>";

document.getElementById('outputarea').innerHTML = ret;

} catch (e) {alert(e);}

},

function (tx,error) {

alert("error fetching rows: " + error.message);

});});

}

catch (e) { alert("Error fetching rows: " + e);}

}

The SQL statement is passed as the first argument B. In this case, we’re pulling two columns from the table called versions. The second parameter C is the Java array holding any available parameterized values. In this sample, there are no parameter- ized values to pass along to the transaction, but you’ll see one in the next section.

Upon a successful execution of the select query, the results function is invoked D.

The second parameter to the results function, which is named result in this code, provides access to the returned record set. The result object contains a collection of rows. Looping over the result set is as easy as walking through an array E. Each row is pulled out of the result set F and individual columns are extracted by name G.

The fourth and final argument to the transaction method is the error handler.

Like the success handler, this is also a callback function that takes two parameters.

The first parameter is again the transaction identifier, and the second is an object rep- resenting the trapped error.

With this basic understanding of the database object’s transaction method, let’s review the remaining functions contained in the db.js file.

Listing 16.7 Processing returned rows from a query

SQL statement

B

Optional parameters

C

Result function

D

Process result rows

E

Work with one row Append F

formatted string

G

16.3.6 Inserting and deleting rows

Thus far you’ve seen how to open a database, create a table, and select rows from the table. Let’s round this example out with an examination of the code required to insert a row and to remove all the rows from the table. The following listing shows the save- Record and deleteAllRecords functions.

function saveRecord(id,name) { try {

db.transaction(function (tx) {

tx.executeSql("insert into versions (id,codename) values (?,?)", [id,name],

function(tx,result) { entryCount++;

document.getElementById('outputarea').innerHTML = "# of rows : "

+ entryCount;

},

function(tx,error) {

alert("Error attempting to insert row" + error.message);

});});

} catch (e) { alert (e); } }

function deleteAllRecords() { try {

db.transaction(function (tx) {

tx.executeSql("delete from versions", [],

function(tx,result) { entryCount = 0;

document.getElementById('outputarea').innerHTML = "# of rows : "

+ entryCount;

},

function(tx,error) {

alert("Error attempting to delete all rows" + error.message);

});});

} catch (e) { alert (e); } }

Inserting a row into our sample database takes place in the saveRecord method B. This method takes two arguments: id and name. A parameterized SQL insert statement

C is crafted providing a placeholder for each of the values required for the versions

table. The parameters themselves are provided in an array D. The success handler E

is invoked after a successful insertion. When an error occurs during the SQL state- ment’s execution, the user is notified via a simple JavaScript alert F. Of course, more sophisticated error responses can be crafted as desired. In the deleteAllRecords function, you see a delete statement executed G.

Listing 16.8 Data-handling functions

Save record

B

C

Insert SQL statement

Define parameters

D E Update count

F

Define error handler

Delete SQL statement

G

If you’re starting to get the feeling that this is just plain old SQL like you hoped, you’re correct. And remember, this is running in the client side of your browser on your Android device!

Though the code runs happily in the Android browser, your phone isn’t necessar- ily the most expedient way of testing the core functionality outside of the visual appearance. Testing on either a real Android device or the emulator provides an acceptable experience, but for web applications such as this one, there’s a better way to test: WebKit on the desktop.

16.3.7 Testing the application with WebKit tools

The good news about SQL development is that you can do it; the bad news is that the tools are limited compared to other SQL environments you’re accustomed to in the desktop world. Fortunately, you can leverage the WebKit desktop browser, and its built-in development tools aid in your database work.

The Web Inspector4 and Error Console found beneath the Develop menu in Web- Kit on the desktop provide helpful tools. When you’re working with JavaScript, one of the challenges is that code tends to die silently. This can happen because something is misspelled or a function doesn’t get properly defined thanks to a parsing error. When working in Eclipse with an Android SDK application, this kind of problem doesn’t occur at runtime because the compiler tells you long before the application ever runs.

With WebKit, you can leverage the Error Console, which provides helpful pointers to parsing problems in JavaScript code. This is one of those “don’t leave home without it” kind of tools.

When you’re working explicitly with a SQL database, the Web Inspector provides a helpful database tool that permits you to peer into the database and browse each of the defined tables. Although this tool isn’t nearly as powerful as tools for commercial databases, there’s something particularly reassuring about seeing your data in the table. Figure 16.9 shows a row in our versions table along with the web application running within the WebKit desktop browser.

The ability to move between the powerful WebKit desktop environment and the Android-based browser is a tremendous advantage to the Android developer looking to create a mobile web application.

As you’ve seen, the ability to store and manage relational data in a persistent fash- ion directly in the browser opens up new possibilities for application creation, deploy- ment, and life-cycle management.

Storing data locally is a tremendous capability, but there may be times when you simply need more raw power and flexibility—and that calls for an SDK-based applica- tion. How do you get the best that HTML, CSS, and JavaScript have to offer but still go deeper? The answer is to build a hybrid application, which we cover next.

4 For more details on the Web Inspector, try http://trac.webkit.org/wiki/WebInspector.

Một phần của tài liệu Manning android in action 3rd (Trang 478 - 487)

Tải bản đầy đủ (PDF)

(662 trang)