Using Google APIs in an iPhone App
March 25th, 2009 | Published in Google Mac Blog
Guest post by Tom Saxton, Idle Loop Software Design
In September, my small software company shipped our first iPhone app, a grocery list program called Grocophile. One of the most common requests from our users was the ability to exchange data over the Internet. Greg Robbins of Google's Mac team suggested that the Google Docs API might be useful, so I jumped in and took a look.
In September, my small software company shipped our first iPhone app, a grocery list program called Grocophile. One of the most common requests from our users was the ability to exchange data over the Internet. Greg Robbins of Google's Mac team suggested that the Google Docs API might be useful, so I jumped in and took a look.
This turned out to be a great way to give our users access to free Internet storage, letting them back up their data and share it across multiple devices. To return the favor, I'd like to share my experience: the learning process, getting the code working on the iPhone, and how I found what I needed from what Google generously provides.
Greg helped me get started by pointing out some online resources. I started with the Objective-C library's overview slideshow. Then I read the Objective-C client introduction, which explains how to get the Google Data APIs library into an iPhone Xcode project. Finally, I downloaded the library sources.
There's a sample app that you'll get with the sources that shows how to talk to Google Docs. The file of interest is DocsSampleWindowController. Start by looking at the two methods "uploadFileAtPath:" and "saveSelectedDocumentToPath:", as those demonstrate how to upload and download files, respectively.
The code is part of a Mac OS X Cocoa app, so it has some Mac-specific code intermingled with the GData code. To bring it into an iPhone project, I trimmed out the Mac user interface stuff, and defined a class and a protocol to create code that should work from any Mac or iPhone application.
The library requires several steps to upload or download a file. First, you create a service object that encodes the user agent that identifies your application, along with the username and password for the account you want to access. Then use that object to request a document list feed, which is the list of documents in the user's account.
Retrieving the document list feed both validates the account credentials and captures information you'll need to either upload or download files. The feed contains the URL for downloading each document. To download, you can use any http call such as NSURLConnection or the library's GDataHTTPFetcher. The feed also has the URL for uploading new document entries.
The networking operations are asynchronous, so my encapsulating object has methods for starting an upload or download, then uses an Objective-C protocol to inform the controlling object of the progress and status at completion.
I've been calling these document objects files, but Google Docs isn't a file system. It's much more like a web publishing system: a collection of objects with associated metadata including title, creation and modification dates, and so on.
The first difference from a file system that I encountered is that you can have multiple different files with the same name. So, if you just upload a new version of a file the same way you uploaded it the first time, you'll get a second document with that same name. To avoid that, search for the document entry with the title that you want to upload to. You can then request an update operation instead of an insert.
The second issue I discovered is that much like when you post to a blog, what you upload can get transformed to match the type of document that is holding the data. When you download the same object, you get something different than what you uploaded. For example, I uploaded a plain text document (specified by MIME type "text/plain"), but when I downloaded that same object I found the text wrapped in a bunch of HTML that makes it display well on the Google Docs web page. Our app's files are UTF-8 XML files created by NSKeyedArchiver. Google Docs fails if you try to specify a MIME type of "text/xml" and totally mangles the document contents if you specify "text/plain". That is not a big surprise because there's not currently a way to specify that the text is encoded UTF-8, and the content gets stuffed into an XML file for the journey to the server.
I solved this issue by converting my files into a plain ASCII encoding, wrapping that in HTML which explains that the file our users see in the Google Docs web page is a Grocophile data file and isn't user editable, and uploading that as "text/html". When I download this file, the HTML does pick up a bunch of Google additions, but it's a simple matter to scan the file to find my encoded document contents.
There's a sample app that you'll get with the sources that shows how to talk to Google Docs. The file of interest is DocsSampleWindowController. Start by looking at the two methods "uploadFileAtPath:" and "saveSelectedDocumentToPath:", as those demonstrate how to upload and download files, respectively.
The code is part of a Mac OS X Cocoa app, so it has some Mac-specific code intermingled with the GData code. To bring it into an iPhone project, I trimmed out the Mac user interface stuff, and defined a class and a protocol to create code that should work from any Mac or iPhone application.
The library requires several steps to upload or download a file. First, you create a service object that encodes the user agent that identifies your application, along with the username and password for the account you want to access. Then use that object to request a document list feed, which is the list of documents in the user's account.
Retrieving the document list feed both validates the account credentials and captures information you'll need to either upload or download files. The feed contains the URL for downloading each document. To download, you can use any http call such as NSURLConnection or the library's GDataHTTPFetcher. The feed also has the URL for uploading new document entries.
The networking operations are asynchronous, so my encapsulating object has methods for starting an upload or download, then uses an Objective-C protocol to inform the controlling object of the progress and status at completion.
I've been calling these document objects files, but Google Docs isn't a file system. It's much more like a web publishing system: a collection of objects with associated metadata including title, creation and modification dates, and so on.
The first difference from a file system that I encountered is that you can have multiple different files with the same name. So, if you just upload a new version of a file the same way you uploaded it the first time, you'll get a second document with that same name. To avoid that, search for the document entry with the title that you want to upload to. You can then request an update operation instead of an insert.
The second issue I discovered is that much like when you post to a blog, what you upload can get transformed to match the type of document that is holding the data. When you download the same object, you get something different than what you uploaded. For example, I uploaded a plain text document (specified by MIME type "text/plain"), but when I downloaded that same object I found the text wrapped in a bunch of HTML that makes it display well on the Google Docs web page. Our app's files are UTF-8 XML files created by NSKeyedArchiver. Google Docs fails if you try to specify a MIME type of "text/xml" and totally mangles the document contents if you specify "text/plain". That is not a big surprise because there's not currently a way to specify that the text is encoded UTF-8, and the content gets stuffed into an XML file for the journey to the server.
I solved this issue by converting my files into a plain ASCII encoding, wrapping that in HTML which explains that the file our users see in the Google Docs web page is a Grocophile data file and isn't user editable, and uploading that as "text/html". When I download this file, the HTML does pick up a bunch of Google additions, but it's a simple matter to scan the file to find my encoded document contents.
Different apps will have different needs for storing their documents. If your app can store and retrieve its data in text, HTML or a spreadsheet, then Google Docs will work well for you. Grocophile's data is basically a relational database with a series of tables and joins keyed off of UUIDs. I could represent the data in text, but it would be fragile and not appropriate for end-user editing. Even though our data won't be editable within Google Docs, there's still plenty of value in being able to back up, restore and merge data sets from Grocophile.
To help out other Mac and iPhone developers, I've published my code for using the library in an iPhone application as an open source project. If you have any questions, or suggestions for improvement, please contact me at idleloop.com.