Source: screaming-penguin.com/screamRDF.txt
Screaming Penguin


DevNexus 2010

So I was one of the speakers at DevNexus 2010. DevNexus is a fairly large (respectively) Atlanta area tech conference that is organized by the folks at AJUG.

My presentation for this conference was about Android, basically a fast whirlwind tour of Android at the 101 level. I put a good deal of effort into trying to distill my 1:10, and the attendees seemed to really enjoy it. Everyone I spoke to said they liked it quite a lot (though I am sure it was too fast in some areas at times, it really was a very ambitious amount of ground to cover in that time frame, an error on my part).

The Prezi is here: Unlocking Android Development Prezi.

Also, a bunch of small focused Android examples that I worked on for the conference (but didn't have time to get to all of in the presentation) are online and open source here: and-examples.

I will likely continue to add to and enhance those examples, and welcome any contributions/feedback/issues, so if you want a quick reference keep an eye on and-examples.

In all I had a great time at DevNexus, I enjoyed presenting, caught a bunch of other great talks (Barry Hawkins agile stuff, James Ward's flex stuff, Neal Ford's general dev stuff, Chris Ramsdale's GWT stuff, Toby Reyelt's App Engine stuff, and more), and really enjoyed the camaraderie and beer into the early morning hours. Awesome show this year, thanks to those that put it on, and all those that attended.

Hope to see you there next time!



Backing up your Android SQLite database to the SD card

Another useful Android programming tip is to either allow users to selectively backup any database your application uses, or just do it for them in the background (at some interval).

Why is this important? Well, if the user gets a new phone (for whatever reason, lost it, upgraded, chucked it out the window before going kayaking) one of the first things they will do is hit the Android Market again and try to re-download the applications they had (the Market saves this info and makes it easy, well, generally, there are a few quirks, but that is beyond the scope here). Also, they might just want to uninstall your app at some point and later come back to it on an existing phone. Without a database backup they might re-acquire your application but they will be pissed (rightly so) if all the data is missing.

Some applications handle this very well (notably Evan Charlton's Mileage, which is a great app BTW, has always upgraded for me flawlessly -- I don't know if it uses a similar method to what I advocate here or not, but it works very well however it does it), and others don't (Google Listen, I am looking at you ;)).

To that end in this article we are going to modify the AndroidExamples application we have worked on before here at TotSP to include a new Activity that allows users to backup the database. This is really just to demonstrate a few ways to do this rather than an exhaustive example, but it should get you started if you are interested in this area.

Our new version of AndroidExamples (complete code via the link) will have a Menu on the Main activity that lets users go to a new ManageData activity -- as seen in the screen shot below:

Our new ManageData activity, which allows us to backup our database file, AND hands off to another new class in case we want to backup our database as XML, is shown below:

public class ManageData extends Activity { private MyApplication application; private Button exportDbToSdButton; private Button exportDbXmlToSdButton; @Override public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.application = (MyApplication) this.getApplication(); this.setContentView(R.layout.managedata); this.exportDbToSdButton = (Button) this.findViewById(R.id.exportdbtosdbutton); this.exportDbToSdButton.setOnClickListener(new OnClickListener() { public void onClick(final View v) { if (ManageData.this.isExternalStorageAvail()) { new ExportDatabaseFileTask().execute(); } else { Toast.makeText(ManageData.this, "External storage is not available, unable to export data.", Toast.LENGTH_SHORT).show(); } } }); this.exportDbXmlToSdButton = (Button) this.findViewById(R.id.exportdbxmltosdbutton); this.exportDbXmlToSdButton.setOnClickListener(new OnClickListener() { public void onClick(final View v) { if (ManageData.this.isExternalStorageAvail()) { new ExportDataAsXmlTask().execute("exampledb", "exampledata"); } else { Toast.makeText(ManageData.this, "External storage is not available, unable to export data.", Toast.LENGTH_SHORT).show(); } } }); } private boolean isExternalStorageAvail() { return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED); } private class ExportDatabaseFileTask extends AsyncTask<String, Void, Boolean> { private final ProgressDialog dialog = new ProgressDialog(ManageData.this); // can use UI thread here protected void onPreExecute() { this.dialog.setMessage("Exporting database..."); this.dialog.show(); } // automatically done on worker thread (separate from UI thread) protected Boolean doInBackground(final String... args) { File dbFile = new File(Environment.getDataDirectory() + "/data/com.totsp.androidexamples/databases/example.db"); File exportDir = new File(Environment.getExternalStorageDirectory(), "exampledata"); if (!exportDir.exists()) { exportDir.mkdirs(); } File file = new File(exportDir, dbFile.getName()); try { file.createNewFile(); this.copyFile(dbFile, file); return true; } catch (IOException e) { Log.e(MyApplication.APP_NAME, e.getMessage(), e); return false; } } // can use UI thread here protected void onPostExecute(final Boolean success) { if (this.dialog.isShowing()) { this.dialog.dismiss(); } if (success) { Toast.makeText(ManageData.this, "Export successful!", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(ManageData.this, "Export failed", Toast.LENGTH_SHORT).show(); } } void copyFile(File src, File dst) throws IOException { FileChannel inChannel = new FileInputStream(src).getChannel(); FileChannel outChannel = new FileOutputStream(dst).getChannel(); try { inChannel.transferTo(0, inChannel.size(), outChannel); } finally { if (inChannel != null) inChannel.close(); if (outChannel != null) outChannel.close(); } } } private class ExportDataAsXmlTask extends AsyncTask<String, Void, String> { private final ProgressDialog dialog = new ProgressDialog(ManageData.this); // can use UI thread here protected void onPreExecute() { this.dialog.setMessage("Exporting database as XML..."); this.dialog.show(); } // automatically done on worker thread (separate from UI thread) protected String doInBackground(final String... args) { DataXmlExporter dm = new DataXmlExporter(ManageData.this.application.getDataHelper().getDb()); try { String dbName = args[0]; String exportFileName = args[1]; dm.export(dbName, exportFileName); } catch (IOException e) { Log.e(MyApplication.APP_NAME, e.getMessage(), e); return e.getMessage(); } return null; } // can use UI thread here protected void onPostExecute(final String errMsg) { if (this.dialog.isShowing()) { this.dialog.dismiss(); } if (errMsg == null) { Toast.makeText(ManageData.this, "Export successful!", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(ManageData.this, "Export failed - " + errMsg, Toast.LENGTH_SHORT).show(); } } } }

Basically this activity is really boring in terms of UI, just two Buttons.

If the user selects the first button the SQLite database is just copied to the SD card. This is done via the ExportDatabaseFileTask inner class. This is another use of AsyncTask, the handy background task helper class that we saw introduced in the last tutorial of this series.

Within this task class we see that we obtain a reference to the /data/data/PACKAGE_NAME/databases/DB_NAME file starting from the Environment.getDataDirectory() method. Once we have a handle to the file we use a simple internal method to copy it to a new file located on the SD card. This is important, an SQLite database *is* JUST a file on the filesystem, so you don't need anything fancy to back it up -- just copy the file to the SD card.

The second button on our activity is a little more complicated. This allows the user to backup the data as an XML file using another AsyncTask, ExportDataAsXmlTask. As opposed to a regular simple file backup, an XML representation might be useful in some other contexts (such as later using XSLT to transform the data into another format for import to other systems). The XML version CAN also be used as a backup/restore method, but again, that's really more complicated than necessary. The DataXmlExporter class our task uses to covert our database into an XML representation is shown below:

public class DataXmlExporter { private static final String DATASUBDIRECTORY = "exampledata"; private SQLiteDatabase db; private XmlBuilder xmlBuilder; public DataXmlExporter(SQLiteDatabase db) { this.db = db; } public void export(String dbName, String exportFileNamePrefix) throws IOException { Log.i(MyApplication.APP_NAME, "exporting database - " + dbName + " exportFileNamePrefix=" + exportFileNamePrefix); this.xmlBuilder = new XmlBuilder(); this.xmlBuilder.start(dbName); // get the tables String sql = "select * from sqlite_master"; Cursor c = this.db.rawQuery(sql, new String[0]); Log.d(MyApplication.APP_NAME, "select * from sqlite_master, cur size " + c.getCount()); if (c.moveToFirst()) { do { String tableName = c.getString(c.getColumnIndex("name")); Log.d(MyApplication.APP_NAME, "table name " + tableName); // skip metadata, sequence, and uidx (unique indexes) if (!tableName.equals("android_metadata") && !tableName.equals("sqlite_sequence") && !tableName.startsWith("uidx")) { this.exportTable(tableName); } } while (c.moveToNext()); } String xmlString = this.xmlBuilder.end(); this.writeToFile(xmlString, exportFileNamePrefix + ".xml"); Log.i(MyApplication.APP_NAME, "exporting database complete"); } private void exportTable(final String tableName) throws IOException { Log.d(MyApplication.APP_NAME, "exporting table - " + tableName); this.xmlBuilder.openTable(tableName); String sql = "select * from " + tableName; Cursor c = this.db.rawQuery(sql, new String[0]); if (c.moveToFirst()) { int cols = c.getColumnCount(); do { this.xmlBuilder.openRow(); for (int i = 0; i < cols; i++) { this.xmlBuilder.addColumn(c.getColumnName(i), c.getString(i)); } this.xmlBuilder.closeRow(); } while (c.moveToNext()); } c.close(); this.xmlBuilder.closeTable(); } private void writeToFile(String xmlString, String exportFileName) throws IOException { File dir = new File(Environment.getExternalStorageDirectory(), DATASUBDIRECTORY); if (!dir.exists()) { dir.mkdirs(); } File file = new File(dir, exportFileName); file.createNewFile(); ByteBuffer buff = ByteBuffer.wrap(xmlString.getBytes()); FileChannel channel = new FileOutputStream(file).getChannel(); try { channel.write(buff); } finally { if (channel != null) channel.close(); } } class XmlBuilder { private static final String OPEN_XML_STANZA = "<?xml version=\"1.0\" encoding=\"utf-8\"?>"; private static final String CLOSE_WITH_TICK = "'>"; private static final String DB_OPEN = "<database name='"; private static final String DB_CLOSE = "</database>"; private static final String TABLE_OPEN = "<table name='"; private static final String TABLE_CLOSE = "</table>"; private static final String ROW_OPEN = "<row>"; private static final String ROW_CLOSE = "</row>"; private static final String COL_OPEN = "<col name='"; private static final String COL_CLOSE = "</col>"; private final StringBuilder sb; public XmlBuilder() throws IOException { this.sb = new StringBuilder(); } void start(String dbName) { this.sb.append(OPEN_XML_STANZA); this.sb.append(DB_OPEN + dbName + CLOSE_WITH_TICK); } String end() throws IOException { this.sb.append(DB_CLOSE); return this.sb.toString(); } void openTable(String tableName) { this.sb.append(TABLE_OPEN + tableName + CLOSE_WITH_TICK); } void closeTable() { this.sb.append(TABLE_CLOSE); } void openRow() { this.sb.append(ROW_OPEN); } void closeRow() { this.sb.append(ROW_CLOSE); } void addColumn(final String name, final String val) throws IOException { this.sb.append(COL_OPEN + name + CLOSE_WITH_TICK + val + COL_CLOSE); } } }

The inspiration for this approach came from this blog article: http://mgmblog.com/2009/02/06/export-an-android-sqlite-db-to-an-xml-file.... Our code is significantly different from the original, but you can see the core of the idea there -- thanks go to mmaitlen for that one.

For both of these approaches you may have also noticed that we first check whether external storage is available with the isExternalStorageAvailable method on the ManageData activity. This is a useful tip, you should always check that there IS external storage, and that it is available, before attempting to use it.

If you try this, you can then use the DDMS explorer (or the adb shell) to poke around and see the backup files in the /sdcard/exampledata location. Once the backup files are there, you can also do this same "task" in reverse and copy the database from the SD card back to the /data/data/APPNAME/databases location to restore it. The only trick there is to make sure there are no open database operations when you backup/restore, and then after you restore you need to close and re-open the database.

Overall this is another quick and simplified example, it isn't intended to be complete or comprehensive, but hopefully it may provide some direction for you to explore these topics on your own and make apps that work a little better for your users.

(And, when I get a bit more time the next article in this series will involve checking for the existence of a database backup at the first app startup and if found importing/using it.)



The American Internet (tm)

While I am catching up on stuff I meant to post about, I just want to make a quick note about this post by Kevin Drum at MoJo. The thing that really stood out was the included quote he cited:

Nancy Scola uses Iran's recent ban on Gmail as an occasion to say this:

I've been squawking recently about the rising time of anti-Internet rhetoric that is at its core anti-American Internet rhetoric, and how that's something that those of us who love the Internet should perpare ourselves to deal with. We saw it with China, when they responded to a possible Google pullout by complaining that the World Wide Web is hopelessly flooded with American content, and we see it again and again in Cuba, where the Castro regime argues that the content on the Web is so skewed toward American interests that they just don't want it for their people. From the perspective of Beijing or Havana, it's as if you turned on a TV in New York City and 470 of 500 channels were running Latin American telenovelas. More local, non-English content would be good for everyone involved.

There are about five things that are wrong with this whole line of thinking. First, the "Internet" even if you want to paint with the broadest of brushes, isn't "American," it is "Anglophonic/Anglographic." People in the UK or AU or NZ don't care much about where stuff comes from, as long as they can access it in their language. The NYC TV example here is cute, but it misses the point. If 470 of those 500 channels were Brit or Kiwi channels, most people wouldn't mind.

The main difference here is that when you are in Havanna or Rio, the most TV you see on the local cable is in Spanish. If you are in Tehran or Damascus it will be in Arabic. Sure, the Internet is mostly English language, because those of the people producing content. That doesn't even really make it an "Anglophic Internet." It just means that Iran and Cuba and anywhere else aren't producing content locally. Nothing is stopping them from building the "Internet version of telenovellas" to run right along side the "Internet version of Wife Swap."

The real analogy for the NYC TV for other parts of the world would be "There are 16 million channels and only 750,000 of them are in my language." We leaping lizards, you have 750,000 channels!



No, THEY are the Stupid Ones

Blah blah blah, iPad. Whatever.

Much linked article at Funkatron this week about users and how computers are appliances and we have been giving them the wrong thing for three decades.

Sweet Jesus I get sick of hearing this.

First, it isn't our fault that people want more computer than they can use. For years people have sauntered into Radio Shack or Best Buy equipped with some barely understood lingo about MagnaRAMs and JigaPixEls and bought computers. They buy general purpose computers because they see all the cool things people can do with them. Jobs and Apple since 1984 have been trying to sell the idea that a computer is an appliance, like a toaster. That is fine, but that is incredibly hard. People could have purchased Word Processor appliances. Email appliances. Chat appliances. These have all been marketed. The problem is people know better than to buy those, even if they aren't 100% comfortable with hierarchical file systems.

When the automobile first came out, it was a hobbyist vehicle. You had to really understand how to deal with an ICE to even own one. Today, you don't even have to know how to work a clutch, and still, it takes weeks or months of training for a teenager to learn to drive. It cars are so damned hard to operate, not to mention dangerous, why do we even have them? Because they can take you anywhere. If people really wanted ease of operation, almost everyone would all take the bus. They don't. They want the power to go where ever they want whenever they want. If an automatic transmission makes that easier, they will use it. If it make it easier but limits them, they will learn to drive a stick.

Sure, software has gotten easier to use. As the common cases for software are narrowed down, it gets refined. But in the large, what drives people to new software isn't the ease of use, it is doing something different. Maybe there are a large number of people who wouldn't play LPMUD but would play World of Warcraft. Woohoo, but that doesn't mean there weren't millions and millions of people who drove the development between the two.

The iPad is computing appliance. More so than anything else perhaps ever marketed. That is great, but that also means it will forever be nailed to the known factors of computing. It will never be a device for innovating in what you can do with a computing device, because it will never be a general purpose device. You can't innovate in automotive technology if you can't open the hood. You can't even realistically develop new racing strategies if you can't change your tires.

The iPad might do well. But the iPad is a bus -- a bus with comfy seats and Isaac the bartender tossing you a pina colada in the back. It won't take you everywhere, but it will get you maybe 70% of the way there, but it won't take you to the happening nightspots that everyone is talking about.



Android Application and AsyncTask basics

Programming on Android is pretty easy. At least it is at first, then the subtleties creep in -- like maintaining state, long running tasks, managing orientation changes, and so on. When you get to that stuff programming Android gets, well, not hard, but let's say more complicated.

The Android APIs are generally very nice, and there are ways to deal with just about any situation, it's just that they aren't always obvious. In this tutorial we will expand on the last one we worked on which covered SQLite and using a database. Here we are going to fill out more details, including adding an android.app.Application object to create expensive objects and maintain state, adding a few more data operations, and putting said data operations onto a background Thread using AsyncTask (plus demonstrating saving instance state when the screen orientation is changed, and restoring same).

The complete application, which is intended to be very simple, will look like the screen shot below:

As you can see this example allows us to enter data, then press "Save," and that data is stored in a database (for details about how the database works see the previous example article, or the DataHelper code). Also, this example shows the current data from the database, and allows us to clear/delete it.

Again, this is a simplified example that is intended to give you an idea of what the possibilities and related problems with long running tasks are, and an overview of how to use the Application object. For more complete information, as always, see the Android docs (specifically for AsyncTask management stuff, and you might also want to check out Droid-Fu which is an excellent little utility library that helps, and goes much farther than this example, with AsyncTask management).

Ok, so on with our example. The complete code for this project, in case you just want to jump in that way, can be found here: http://totsp.com/svn/repo/AndroidExamples/trunk/.

NOTE The code for this example was updated for part 3 of this series and what is in SVN is now a little different from this example. See part 3 for details on what changed.

The first notable code with this update is the Main Activity, as seen below:

public class Main extends Activity { private static final String NAME = "NAME"; private EditText input; private Button saveButton; private Button deleteButton; private TextView output; private MyApplication application; @Override public void onCreate(final Bundle savedInstanceState) { Log.d(MyApplication.APP_NAME, "onCreate"); super.onCreate(savedInstanceState); this.setContentView(R.layout.main); // get "Application" object for shared state or creating of expensive resources - like DataHelper // (this is not recreated as often as each Activity) this.application = (MyApplication) this.getApplication(); // inflate views this.input = (EditText) this.findViewById(R.id.in_text); this.saveButton = (Button) this.findViewById(R.id.save_button); this.deleteButton = (Button) this.findViewById(R.id.del_button); this.output = (TextView) this.findViewById(R.id.out_text); // initially populate "output" view from database new SelectDataTask().execute(); // save new data to database (when save button is clicked) this.saveButton.setOnClickListener(new OnClickListener() { public void onClick(final View v) { new InsertDataTask().execute(Main.this.input.getText().toString()); Main.this.input.setText(""); } }); // delete all data from database (when delete button is clicked) this.deleteButton.setOnClickListener(new OnClickListener() { public void onClick(final View v) { new DeleteDataTask().execute(); } }); } @Override public void onSaveInstanceState(final Bundle b) { Log.d(MyApplication.APP_NAME, "onSaveInstanceState"); if ((this.input.getText().toString() != null) && (this.input.getText().toString().length() > 0)) { b.putString(Main.NAME, this.input.getText().toString()); } super.onSaveInstanceState(b); } @Override public void onRestoreInstanceState(final Bundle b) { Log.d(MyApplication.APP_NAME, "onRestoreInstanceState"); super.onRestoreInstanceState(b); String name = b.getString(Main.NAME); if (name != null) { // use onSaveInstanceState/onRestoreInstance state to manage state when orientation is changed (and whenever restarted) // put some text in input box, then rotate screen, text should remain // COMMENT this out, and try again, text won't be there - you need to maintain this state - esp for orientation changes // (you can rotate the screen in the emulator by pressing 9 on numeric keypad) this.input.setText(name); } } private class InsertDataTask extends AsyncTask<String, Void, Void> { private final ProgressDialog dialog = new ProgressDialog(Main.this); // can use UI thread here protected void onPreExecute() { this.dialog.setMessage("Inserting data..."); this.dialog.show(); } // automatically done on worker thread (separate from UI thread) protected Void doInBackground(final String... args) { Main.this.application.getDataHelper().insert(args[0]); return null; } // can use UI thread here protected void onPostExecute(final Void unused) { if (this.dialog.isShowing()) { this.dialog.dismiss(); } // reset the output view by retrieving the new data // (note, this is a naive example, in the real world it might make sense // to have a cache of the data and just append to what is already there, or such // in order to cut down on expensive database operations) new SelectDataTask().execute(); } } private class SelectDataTask extends AsyncTask<String, Void, String> { private final ProgressDialog dialog = new ProgressDialog(Main.this); // can use UI thread here protected void onPreExecute() { this.dialog.setMessage("Selecting data..."); this.dialog.show(); } // automatically done on worker thread (separate from UI thread) protected String doInBackground(final String... args) { List<String> names = Main.this.application.getDataHelper().selectAll(); StringBuilder sb = new StringBuilder(); sb.append("Names in database:\n"); for (String name : names) { sb.append(name + "\n"); } return sb.toString(); } // can use UI thread here protected void onPostExecute(final String result) { if (this.dialog.isShowing()) { this.dialog.dismiss(); } Main.this.output.setText(result); } } private class DeleteDataTask extends AsyncTask<String, Void, Void> { private final ProgressDialog dialog = new ProgressDialog(Main.this); // can use UI thread here protected void onPreExecute() { this.dialog.setMessage("Deleting data..."); this.dialog.show(); } // automatically done on worker thread (separate from UI thread) protected Void doInBackground(final String... args) { Main.this.application.getDataHelper().deleteAll(); return null; } // can use UI thread here protected void onPostExecute(final Void unused) { if (this.dialog.isShowing()) { this.dialog.dismiss(); } // reset the output view by retrieving the new data // (note, this is a naive example, in the real world it might make sense // to have a cache of the data and just append to what is already there, or such // in order to cut down on expensive database operations) new SelectDataTask().execute(); } } }

In this code we start with a typical onCreate method that inflates views and includes a Button click listener. But, you might also have noticed we are setting up a "MyApplication" object which is an instance of Application. The android.app.Application object is very useful, for many reasons. You can stick state there and share it between activities (you can do this with getters and setters, simple), and you can use it to instantiate objects that are needed by your app in more than one activity and are relatively expensive to create. The context of the Application object is "lives" as long as your application, and is different from the context of any single activity. For our simple example we only have one activity, but we are using the application object to demonstrate what the possibilities are. To use the application object you need to extend it, and you need to add android:name="[ApplicationClassName] to the "application" element of your manifest. Our "MyApplication" object for this example is shown below:

public class MyApplication extends Application { public static final String APP_NAME = "AndroidExamples"; private DataHelper dataHelper; @Override public void onCreate() { super.onCreate(); Log.d(APP_NAME, "APPLICATION onCreate"); this.dataHelper = new DataHelper(this); } @Override public void onTerminate() { Log.d(APP_NAME, "APPLICATION onTerminate"); super.onTerminate(); } public DataHelper getDataHelper() { return this.dataHelper; } public void setDataHelper(DataHelper dataHelper) { this.dataHelper = dataHelper; } }

Real simple. Here we *could* add getters and setters and store state, but basically we are using the longer lifecycle of the application object to create our DataHelper object and make it available to activities. Application let's you do much more than this, but the basics are state and longer life-cycle (see the docs for more info). Even though application is very useful, and very simple, it's often overlooked.

The next significant part of our new Main activity (going back to the previous code snippet above) is that it no longer calls data operations directly. Instead, it uses several inner AsyncTask classes to move these operations OFF of the main UI thread and onto another thread where they can run asynchronously.

This is HUGE. Ok, our sample application doesn't have anything fancy/long running to do, so it's overkill for the purposes of the example here, but in real life these data operations might be a lot more complicated and time consuming. And, you can use AsyncTask for anything, network operations are another common case.

AsyncTask replaces a lot of manual Handler/Thread/wiring that was needed in the original Android SDKs - it's a convenience for you. AsyncTask has an execute method that performs your long running task on a thread SEPARATE from the main UI thread automatically, and has clear pre and post methods where you CAN interact with the main UI thread too. From the example code, where we have 3 AsyncTask instances for our different data operations, the pattern should be clear (another approach might have been to have ONE task here, and to use the execute method arguments to determine the operation -- that would also allow us to manage the task state itself better, but that is getting beyond the scope of this example).

After the new task code we also have now implemented the onSaveInstanceState and onRestoreInstanceState methods of Activity. These are for instance state only (some call it "transient" state -- that may seem an odd turn of phrase, but this is state only associated with current instance of the activity, not persistent state, for that we are using the database). These allow us to save simple UI state, such as the contents of a text view, or what checkbox is checked, in case the activity is restarted suddenly, in mid course. This wouldn't really be all that important except for the fact that any orientation change does *exactly this* (yes, it restarts your activity and the life-cycle happens again). For this simple example we have only one input field ("input") to worry about, but you can see how this could be applied to any number of elements. To see exactly what this does, run this example several times and try commenting out the onRestoreInstanceState method. Enter some text in the input box, and then change the screen orientation -- without this method the text disappears.

And, that does it for this time. Our new AndroidExamples application now has a slightly more useful user interface that allows us to enter and delete data, and displays it all. Additionally we are now using the Application object to create our DataHelper, and our helper performs all of the data management tasks we need (creating the database, and basic CRUD operations). Also, every time we invoke data related methods we are now doing this with AsyncTask instances, so that these operations are not blocking the UI thread. This is still an oversimplified example, of course, and we are really only scratching the surface, but I do hope it might provide some insight as to where you can start to clean up your Android apps and deal with the sticky spots.



Vega

This is the tablet I am really waiting for. Again, I can't stand the limited storage, but at least it has an SD slot:


<!--break-->



Obligatory Hitler

iPad: Maybe for thee, but not for me.

I have to admit, I am really disappointed in the iPad. The short summary: I want a small Mac, not a big iPod.

The form factor is great. I have always thought the MID market was wrong on the size factor. 7-8" screens are too small. 9.7 is just about right. The battery life -- Apple says 10 hours, so I am figuring 6 or 7 in the real world -- seems OK, but not great. Everyone on the interwebs is talking about how fast it is, that is good.

There are, however, MUTLIPLE deal breakers for me on the iPad:

First, storage. 64GB is the top end, and that is worthless for a device of this class. It would have been much better if they had added another pound to the weight and put in a bigger battery and a 300GB HD. Yeah, you got a 720p screen, but you can't store more than 4 or 5 HD movies on the device. That wouldn't be bad, except, WAIT! Unlike the Apple TV, you can't do network syncing/streaming, so moving content onto the device requires doing to your desktop.

Second, the fracking App store. The App store crap is 60% of the reason I left the iPhone for an Android phone, and I don't regret it at all. I certainly don't want to get into another platform where Apple rules with an iron fist. Is Apple going to reject the "Kindle for iPad" because it duplicates the functionality of iBook? I don't need this shit.

Third, iPhone OS: No multitasking and background apps on this "Tablet class" computer? Drive around.

Really the question for me is, "Where does this fit in my life?" I have a phone, a Kindle, an iPod and a laptop that make the trip to and from home everyday with me. If the iPad could replace two of those devices, sure. As it is, I can't see a single function that the iPad can perform better than one of the devices I already have.

There are of course other questions: Why is there no user facing CCD and iChat support? Why is there no WiFi syncing? Why is there no HDMI port on the thing?

Listening to Leo Laporte, his usual stable of idiots are saying this will kill the MacBook Air. Whatever. The Air is a computer, this is a PDA. Actually, if the iPad was an Air sans keyboard, THAT would be an exciting product.



Android SQLite Basics: creating and using a database, and...

Because I often have to revisit this stuff myself, I thought I would write a quick reference tutorial on creating and using a database with an Android application. This isn't terribly well covered in the Android docs, and though many ContentProvider tutorials exist (such as the Unlocking Android code for chapter 5, and the NotePad tutorial included with the SDK), and these help a lot with general database concepts, they are really more complicated than what a basic application needs - a database to store and retrieve stuff.

I will walk through the code and tools for an oversimplified example here, with the ultimate goal of inserting and retrieving some data from an database in an Android app, and then examining the database using a shell and the sqlite3 command line tool. The entire code for this example is available here: http://totsp.com/svn/repo/AndroidExamples/trunk/.

NOTE This code was updated for part 2, and part 3 of this series, so it no longer exactly matches this example -- it's still a working sample app, it just now does a bit more than the original.

First, to get this rolling, we need to create an Android application that HAS a database. We could use any built in application that has a database just to explore it, such as com.android.alarmclock), but we are going to create one here for completeness. After it's setup, the interface for our application will look like the screen shot shown below:

Yeah this project is ugly, and it only has one Activity, but for our purposes here we really aren't trying to create a fancy UI (or anything that is complicated on any level). To create this project we will use the Eclipse IDE. Along with Eclipse, we will also of course need to have the Android SDK with the correct Eclipse ADT Plug-In as a prerequisite too (to get that see the instructions at the aforelinked site).

1. To create a basic Android project, we will simply select File->New->Other->Android->Android Project. On the dialog we will then enter the application name AndroidExamples, and the package name com.totsp.androidexamples, along with some other settings, as shown in the figure below:

The target we choose must be one that we have installed when we setup or configued the Android SDK. For this example we are using 1.6 because it is still the most common Android platform that user's phones are running (we are using the 2.1 SDK, with a 1.6 Target). We will also set the "Min SDK Version" to "4" which is SDK 1.6. This is a confusing part of the Android setup at this point (names are transposed from "sdk" to "api" and such) but the details are documented somewhat.

Once we have the default sample project in place, the next step will be to create a helper class that can create the database and encapsulate other SQL details. We will call this class DataHelper. Within this class (at the end) we will include an important inner class that provides a SQLiteOpenHelper. The full code is shown below:

package com.totsp.androidexamples; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteStatement; import android.util.Log; import java.util.ArrayList; import java.util.List; public class DataHelper { private static final String DATABASE_NAME = "example.db"; private static final int DATABASE_VERSION = 1; private static final String TABLE_NAME = "table1"; private Context context; private SQLiteDatabase db; private SQLiteStatement insertStmt; private static final String INSERT = "insert into " + TABLE_NAME + "(name) values (?)"; public DataHelper(Context context) { this.context = context; OpenHelper openHelper = new OpenHelper(this.context); this.db = openHelper.getWritableDatabase(); this.insertStmt = this.db.compileStatement(INSERT); } public long insert(String name) { this.insertStmt.bindString(1, name); return this.insertStmt.executeInsert(); } public void deleteAll() { this.db.delete(TABLE_NAME, null, null); } public List<String> selectAll() { List<String> list = new ArrayList<String>(); Cursor cursor = this.db.query(TABLE_NAME, new String[] { "name" }, null, null, null, null, "name desc"); if (cursor.moveToFirst()) { do { list.add(cursor.getString(0)); } while (cursor.moveToNext()); } if (cursor != null && !cursor.isClosed()) { cursor.close(); } return list; } private static class OpenHelper extends SQLiteOpenHelper { OpenHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL("CREATE TABLE " + TABLE_NAME + " (id INTEGER PRIMARY KEY, name TEXT)"); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { Log.w("Example", "Upgrading database, this will drop tables and recreate."); db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME); onCreate(db); } } }

This class is simple, as I have said it would be. For example it's using a database with one table and one column, but, it still covers some core Android concepts. We won't go into great detail concerning this class here, it should be mostly understandable from the code, but a few important things to note are:

it includes an implementation of SQLiteOpenHelper as an inner class it demonstrates two different ways of interacting with the database in code, with a SQLiteStatement for inserts (which has the advantage of being pre-compiled, versus regular, but easier SQLiteDatabase.query() methods you probably also want to be familiar with), and directly by querying for selects it shows a useful pattern (though again, oversimplified here) of exposing data persistence/retrieval methods on the helper

To use this class from the default Main.java class that we let the Android Eclipse Plug-In generate, we will modify it a bit to create an instance of our DataHelper and then use it to create and retrieve data as seen below.(NOTE: In the real world you might do this once per application, say by using the oft overlooked Android Application class, where you could create the DataHelper once, and then expose the reference to other classes, rather than in Activity.onCreate()):

package com.totsp.androidexamples; import android.app.Activity; import android.os.Bundle; import android.util.Log; import android.widget.TextView; import java.util.List; public class Main extends Activity { private TextView output; private DataHelper dh; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); this.output = (TextView) this.findViewById(R.id.out_text); this.dh = new DataHelper(this); this.dh.deleteAll(); this.dh.insert("Porky Pig"); this.dh.insert("Foghorn Leghorn"); this.dh.insert("Yosemite Sam"); List<String> names = this.dh.selectAll(); StringBuilder sb = new StringBuilder(); sb.append("Names in database:\n"); for (String name : names) { sb.append(name + "\n"); } Log.d("EXAMPLE", "names size - " + names.size()); this.output.setText(sb.toString()); } }

For this class to work, we also need to change the main.xml layout file it relies on. We need to include one additional TextView for the output, as seen below:

<?xml version="1.0" encoding="utf-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content"> <LinearLayout xmlns:android= "http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/hello" /> <TextView android:id="@+id/out_text" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="" /> </LinearLayout> </ScrollView>

With that, we have an application that we should be able to launch and run in the emulator, and it should look like the first screen shot we saw above - a basic black screen with white text and a few names as output. The names come from the database, which now exists, and now we can move on to using sqlite3.

Android uses SQLite as it's built in embedded database. If you need to store local application data, rather than going to simpler mechanisms like the file system, or more complicated means such as the network, you use the database.

To examine the database we can login using the shell provided by the Android Debug Bridge. To use this we need the "tools" folder of the SDK on our path (see the SDK documentation if you need more information about that). If we start the app in Eclipse (Run As -> Android Application), and leave it running, we should then be able to login with the command:

ccollins@crotalus:~$ adb -e shell #


The -e option tells ADB to use the emulator, rather than a possible connected device (and returns an error if more than one emulator is running). The "#" is the command prompt, we are logged in.

Once logged in we can browse around with the "ls" command. The directory we are interested in is /data/data/com.totsp.androidexamples/databases (each application has a directory at the path /data/data/PACKAGE_NAME). We can change to that directory with the "cd" command. Once there we can use the command line SQLite tool, sqlite3, to examine our database, as follows:

# sqlite3 example.db SQLite version 3.5.9 Enter ".help" for instructions sqlite> select * from sqlite_master; table|android_metadata|android_metadata|3|CREATE TABLE android_metadata (locale TEXT) table|table1|table1|4|CREATE TABLE table1 (id INTEGER PRIMARY KEY, name TEXT) sqlite>

We login with "sqlite3 [database_name]" and then we can run basic SQL commands that are supported by SQLite (see the documentation there for full details). One handy table is the sqlite_master that we can see shows all the other tables inside our database.

Some other interesting commands are:

sqlite> .schema CREATE TABLE android_metadata (locale TEXT); CREATE TABLE table1 (id INTEGER PRIMARY KEY, name TEXT); sqlite> .tables android_metadata table1 sqlite> select * from table1; 1|Porky Pig 2|Foghorn Leghorn 3|Yosemite Sam sqlite>

We can use .help from within the sqlite3 shell (it has it's own shell, apart from the emulator shell we are logged in to), to see a full list of commands. Commands that start with "." are built in, and perform a function - such as .schema and .tables that we see above. SQL commands themselves can also be run directly by typing them in -- something we also see above (typed commands are run as soon as a ; is encountered and you press enter).

With that, we have the basics. We have an application that creates a database and stores and retrieves data using it, and we have done a bit of exploring with the SQLite tools in ADB. In the future I hope to expand on this article and add some more involved tables, and further examples such as Android unit testing (another not so well documented, but extremely useful, part of the platform).



Google. Wow.

Never thought I would be posting about Google in the "Politics" topic, but hey.

In response to a wide ranging attack on Google's infrastructure, that is never explicitly blamed on the Chinese government, they are threatening to take their ball and go the fuck home:

Second, we have evidence to suggest that a primary goal of the attackers was accessing the Gmail accounts of Chinese human rights activists. Based on our investigation to date we believe their attack did not achieve that objective. Only two Gmail accounts appear to have been accessed, and that activity was limited to account information (such as the date the account was created) and subject line, rather than the content of emails themselves.

Third, as part of this investigation but independent of the attack on Google, we have discovered that the accounts of dozens of U.S.-, China- and Europe-based Gmail users who are advocates of human rights in China appear to have been routinely accessed by third parties. These accounts have not been accessed through any security breach at Google, but most likely via phishing scams or malware placed on the users' computers.

...

These attacks and the surveillance they have uncovered--combined with the attempts over the past year to further limit free speech on the web--have led us to conclude that we should review the feasibility of our business operations in China. We have decided we are no longer willing to continue censoring our results on Google.cn, and so over the next few weeks we will be discussing with the Chinese government the basis on which we could operate an unfiltered search engine within the law, if at all. We recognize that this may well mean having to shut down Google.cn, and potentially our offices in China.

Seriously. I am awestruck. The Chinese have seriously pissed off The Google. I honestly can't think of another company that would look Wall Street in the face and say, "We are going to tell China to sit and spin."

I have, in this space and personally, defended Google.cn's filtering of search results. They comply with local laws whereever they operate, which includes filtering neo-Nazi material in Germany. God knows what the Auzzies are going to end up with in their current debacle. This, however, really is about the collective Google being pissed. These attacks don't change the nature of legal compliance with filtering, but speak directly to the nature of Rule of Law in China -- only if you work under the presumption that these attacks were the work of the government. I don't know that "Don't be evil" changed between yesterday and today. What changed was redefinition of the rules, and the stirring of ire.

"This far, no farther." Wonder where my Nexus One was fabricated.

I am, however, concerned what Google unilaterally deciding to not filter Google.cn result means to their employees in China. If they have put their staff in a position of facing retribution from the Chinese government, that is galactically irresponsible.



Newsfeed display by CaRP

Computers: Linux
See Computers in Open Directory
Find related sites in Open Directory

Return to News Feeds Home Page
My Sites