Our Google Drive file picker tutorial app
So this is what our tutorial app does:
- Connects to the Google Play Services
- Accesses the Google Drive API
- Displays a file picker showing the user’s Google Drive folders and files
- Enables the user to select a folder or a file
- We then extract the selected item’s metadata and display it in the logCat. Here’s the metadata that we get:
- Drive ID
- Resource ID
- title
- file size
- mime type
The user’s Google Drive folders and files are displayed. Users can then select an item and the item’s metadata is then displayed in the logCat
Here’s the Logcat display showing the selected item’s metdata
Google Play Services
The device running your app must have Google Play Services installed. You can use isGooglePlayServicesAvailable() to check if the necessary version is installed. This method will then return a status code, such as:
- 0 – SUCCESS
- 1 – SERVICE_MISSING
- 2 – SERVICE_VERSION_UPDATE_REQUIRED
- 3 – SERVICE_DISABLED
- 4 - SERVICE_INVALID
You’ll have to decide, based on the status code, what to do next.
You can also include the getErrorDialog() method in the onConnectionFailed() callback which is triggered when your app fails to connect to the Google Play Services. Pass the returned error code to getErrorDialog() and it will display an appropriate error dialog, like this:
Here’s the error dialog that’s displayed if the user’s device does not have Google Play Services installed
Getting started: preparing the foundation
We’re assuming your device has Google Play Services installed and that you’ve registered your app with Google. If you haven’t already done so, you may want to check out our tutorial, Using Google Drive in your apps, where we discuss the foundations.
Our tutorial app has one activity
All the action happens in one activity which Implements two interfaces:
- ConnectionCallbacks – an interface providing callbacks that are triggered when the client is connected to or disconnected from the Google Play Service
- OnConnectionFailedListener – an interface providing a callback to handle connection failures
Let’s get started!
Step 1
Instantiate the GoogleApiClient in the activity’s onCreate() method.
We’ve put all the code needed to instantiate an instance of the GoogleApiClient in our buildGoogleApiClient() method.
Here’s a quick overview of what happens there:
- We create a new Builder which we use to configure our GoogleApiClient
- We add the Google Drive API which we’ll need to use in our app as we want to access the Google Drive
- We add the OAuth 2.0 scope that we’ll need. Essentially this authorizes us to perform certain tasks such as reading from and writing to the Google Drive
- We add the connection callbacks to handle connecting to and from the service
- We add the connection failed listener to deal with when the client fails to connect to the service
Step 2
Once we’ve built our client, we’re ready to connect to the service.
We connect our client to the Google Play Services in the activity’s onStart() method.
Disconnect the client from the service in the activity’s onStop() method.
Monitoring the connection: The ConnectionCallbacks
onConnected()
This is where all the fun happens.
onConnected() is called asynchronously after a successful connection.
Here we build an IntentSender intent which we’ll use to start an Open File Activity.
The Open File Activity will list all the user’s Google Drive files and folders. They’re then able to make a selection from the list.
We specify which Drive items should be selectable by including their mime types in the setMimeType() method.
Once we’ve built the intent, we start the Open File Activity by calling startIntentSenderForResult(), passing the intent and a request code as parameters. This will identify the returned result as coming from the Open File Activity.
onConnectionSuspended()
This callback is triggered when the client is in a temporary disconnected state.
This could happen if there is a problem with the Google Play Services which runs as a background service – the system can kill it at any time.
Don’t worry, the GoogleApiClient will automatically try to reconnect the client to the service.
So what happens if we can’t connect to the service?
onConnectionFailed()
This callback is triggered if there’s an error trying to connect our client to the service.
The ConnectionResult parameter indicates what caused the error. Here are couple of examples:
- API_UNAVAILABLE – one of the API components that you’re trying to connect to is unavailable
- CANCELLED – the client called disconnect() to cancel the connection
- DEVELOPER_ERROR – there’s a problem with the app’s configuration
- INTERNAL_ERROR – an internal error occurred
There’s no way to resolve this problem
We call hasResolution() to check if startResolutionForResult() is able start an intent that the user can interact with to resolve the problem. If it can’t, then we display an error dialog.
There is a possibility of resolving the problem
If it’s a problem that the user can resolve, then we call the startResolutionForResult() method, passing a unique request code as a parameter. This will be used in the onActivityResult() method to identify where the result came from.
startResolutionForResult() starts an intent that presents a possible solution to the user, for example, a request to sign in. Once the user signs in, our activity’s onActivityResult() callback is triggered where we call connect() to connect to the service.
The onActivityResult() callback
Our activity’s onActivityResult() method:
- receives the returned result from the Open File Activity which is closed when the user presses the Select button
- also receives the result returned by the startResolutionForResult() method called in the onConnectionFailed() callback
If the request code is:
- REQUEST_CODE_SELECT – and the resultCode is OK, then we can get the selected item’s information. First we:
- Get the item’s DriveId which identifies the Drive resource
- we use DriveId to get a DriveFile object ( a file in the Drive) which we use to get access to the file’s metadata
- Calling getMetadata() gets the file’s metadata. A metadata object is returned in a PendingResult
- We’re then able to use another PendingResult to fetch the metadata asynchronously
- Finally we can extract the individual values (title, file size and mime type) from the Metadata object
- REQUEST_CODE_RESOLUTION - and the resultCode is OK, means that the user has managed to resolve the connection failure and we can try and connect to the service
I hope that you found this tutorial helpful.
Here's the code for the activity:
package apps101.co.za.driveme; import android.app.Activity; import android.content.Intent; import android.content.IntentSender; import android.os.Bundle; import android.util.Log; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.GooglePlayServicesUtil; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.common.api.PendingResult; import com.google.android.gms.common.api.ResultCallback; import com.google.android.gms.drive.Drive; import com.google.android.gms.drive.DriveFile; import com.google.android.gms.drive.DriveFolder; import com.google.android.gms.drive.DriveId; import com.google.android.gms.drive.DriveResource; import com.google.android.gms.drive.Metadata; import com.google.android.gms.drive.OpenFileActivityBuilder; /*101apps.co.za*/ public class SelectDriveFolder extends Activity implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener{ private static final String TAG = "drive"; private static final int REQUEST_CODE_SELECT = 102; private static final int REQUEST_CODE_RESOLUTION = 103; private GoogleApiClient googleApiClient; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // you can check if play services installed and up to date - returns integer value Log.i(TAG, "Is Google Play Services available and up to date? " + GooglePlayServicesUtil.isGooglePlayServicesAvailable(this)); buildGoogleApiClient(); } /*connect client to Google Play Services*/ @Override protected void onStart() { super.onStart(); Log.i(TAG, "In onStart() - connecting..."); googleApiClient.connect(); } /*close connection to Google Play Services*/ @Override protected void onStop() { super.onStop(); if (googleApiClient != null) { Log.i(TAG, "In onStop() - disConnecting..."); googleApiClient.disconnect(); } } /*Connection callback - on successful connection*/ @Override public void onConnected(Bundle bundle) { Log.i(TAG, "in onConnected() - we're connected, let's do the work in the background..."); // build an intent that we'll use to start the open file activity IntentSender intentSender = Drive.DriveApi .newOpenFileActivityBuilder() // these mimetypes enable these folders/files types to be selected .setMimeType(new String[] { DriveFolder.MIME_TYPE, "text/plain", "image/png"}) .build(googleApiClient); try { startIntentSenderForResult( intentSender, REQUEST_CODE_SELECT, null, 0, 0, 0); } catch (IntentSender.SendIntentException e) { Log.i(TAG, "Unable to send intent", e); } } /*Connection callback - Called when the client is temporarily in a disconnected state*/ @Override public void onConnectionSuspended(int i) { switch (i) { case 1: Log.i(TAG, "Connection suspended - Cause: " + "Service disconnected"); break; case 2: Log.i(TAG, "Connection suspended - Cause: " + "Connection lost"); break; default: Log.i(TAG, "Connection suspended - Cause: " + "Unknown"); break; } } /*connection failed callback - Called when there was an error connecting the client to the service*/ @Override public void onConnectionFailed(ConnectionResult result) { Log.i(TAG, "Connection failed - result: " + result.toString()); if (!result.hasResolution()) { // display error dialog GooglePlayServicesUtil.getErrorDialog(result.getErrorCode(), this, 0).show(); return; } try { Log.i(TAG, "trying to resolve the Connection failed error..."); // tries to resolve the connection failure by trying to restart this activity result.startResolutionForResult(this, REQUEST_CODE_RESOLUTION); } catch (IntentSender.SendIntentException e) { Log.i(TAG, "Exception while starting resolution activity", e); } } /*build the google api client*/ private void buildGoogleApiClient() { Log.i(TAG, "Building the client"); if (googleApiClient == null) { googleApiClient = new GoogleApiClient.Builder(this) .addApi(Drive.API) .addScope(Drive.SCOPE_FILE) .addConnectionCallbacks(this) .addOnConnectionFailedListener(this) .build(); } } /* receives returned result - called by the open file activity when it's exited by user pressing Select. This passes the request code, result code and data back which is received here*/ @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { Log.i(TAG, "in onActivityResult() - triggered on pressing Select"); switch (requestCode) { case REQUEST_CODE_SELECT: if (resultCode == RESULT_OK) { /*get the selected item's ID*/ DriveId driveId = (DriveId) data.getParcelableExtra( OpenFileActivityBuilder.EXTRA_RESPONSE_DRIVE_ID);//this extra contains the drive id of the selected file Log.i(TAG, "Selected folder's ID: " + driveId.encodeToString()); Log.i(TAG, "Selected folder's Resource ID: " + driveId.getResourceId()); // selected file (can also be a folder) DriveFile selectedFile = Drive.DriveApi.getFile(googleApiClient, driveId); PendingResult selectedFileMetadata = selectedFile.getMetadata(googleApiClient); // fetch the selected item's metadata asynchronously using a pending result selectedFileMetadata.setResultCallback(new ResultCallback() { @Override public void onResult(DriveResource.MetadataResult metadataResult) { // get the metadata out of the result Metadata fileMetadata = metadataResult.getMetadata(); // get the details out of the metadata object Log.i(TAG, "File title: " + fileMetadata.getTitle()); Log.i(TAG, "File size: " + fileMetadata.getFileSize()); Log.i(TAG, "File mime type: " + fileMetadata.getMimeType()); } }); } finish(); break; case REQUEST_CODE_RESOLUTION: if (resultCode == RESULT_OK) { Log.i(TAG, "in onActivityResult() - resolving connection, connecting..."); googleApiClient.connect(); } break; default: super.onActivityResult(requestCode, resultCode, data); break; } } }
You can download the rest of the code here