Thursday, May 9, 2013

reading/writing Google Drive

Here are the broad steps to write a file from an Android device to Google Drive.  You create a unique SHA1 fingerprint for your application (step 1), tell Drive your application exists and will be utilising the Drive API (step 2).  The branding information is the "pretty" name for your app; the package name is its name in your code (e.g. com.example.myapp). Library and dependency installation (step 3) is easy if you have Eclipse; if you have IntelliJ, one of their programmers has shown how to create the analogous configuration on StackOverflow.

The sample code, Drive Quickstart, starts up the built-in camera app, grabs the Uri of the saved image, and uploads it to Drive.  In the process it needs to get a login name and authorization to write to Drive.  Since life is uncertain, it uses startActivityForResult, which returns a result code to determine the next action.  I needed significantly more complicated behavior for writing the file, but this is the basic sequence of events.


My application stores the user login (e.g. "mightyjoeyoung@gmail.com") in SharedPreferences, so once it's set, it won't ask again. There are some initial checks if a) a stored name exists and b) the stored name actually associates with a valid account. If it finds nothing, it starts an account picker Intent.


app  = (App) getApplication();
// check for a previously saved login
settings = getSharedPreferences(app.PREFS_NAME, 0);
String savedLogin = settings.getString("savedlogin", "");
if (!savedLogin.equals("")) {
    app.credential.setSelectedAccountName(savedLogin);
    service = getDriveService(app.credential);
}
if (savedInstanceState != null) {
    inProgress = savedInstanceState.getBoolean("inProgress");
}
if (app.credential.getSelectedAccount() == null) {
    startActivityForResult(app.credential.newChooseAccountIntent(), REQUEST_ACCOUNT_PICKER);
}


When a valid account is set, it authorizes and creates a Drive service object

Drive service = new Drive.Builder(AndroidHttp.newCompatibleTransport(), new GsonFactory(), credential).build();

which is used for the reading/writing transactions.  Failure to authorize results in going back to the account picker.

If all is well, it grabs the Uri of the file on the phone (which was created before starting the whole process) and attempts to upload it.

Drive Quickstart's sample code has no UI beyond the bare minimum.  While it's busy trying to upload, by all appearances the app has hung. And the first time it's run, it can take up to a minute to complete the process (or at least that's what happened to me).  They don't even bother to compensate for changes in screen orientation.  With a progress dialog popup and ongoing status messages, users will be less inclined to cancel.

Drive has a quirk (as mentioned in an earlier post) of keeping track of files with an internal ID rather than the file name. Multiple writes of the same file name results in multiple files with the same name.  Rather than confusing the user (and the macro that adds the records to the spreadsheet) I had to get hold of that file ID, save it to SharedPreferences, and when time came for rewriting, write to that ID.  And, if the user had happened to trash it, fish it out of the trash.

So the logic goes: if you have an ID previously saved in SharedPreferences, and that file exists, you're good to go and overwrite. (And fish it out of the trash, whether or not it's in trash, because it's not super easy to tell if it's in trash.)  If you don't have a saved ID, or you do but the file isn't there, you have to create a new file (and after it's written to Drive, get its file ID and save it).

Here is a list of the methods to work with Drive files. There is also some sample code at the end of each method's page.

One weirdness about the Drive Quickstart sample code: for reasons unknown, it asks for full access to all the files on the Drive.
credential = GoogleAccountCredential.usingOAuth2(this, DriveScopes.DRIVE);
It doesn't need this, and your users will be O_o if your app asks for the same permission. You really want your app to access only the files it creates:
credential = GoogleAccountCredential.usingOAuth2(this, DriveScopes.DRIVE_FILE);

Lastly, the boyfriend's phone seemed to need this added to the manifest:
<uses-permission android:name="android.permission.USE_CREDENTIALS"/>
I'm not sure if this was due to his Android version, or that he has more than one account registered on the phone, or that he has two-stage authentication.

No comments:

Post a Comment